fix rtl unicode formatting (#659)
* Isolate usernames when formatting, to improve interaction of RTL usernames with LTR locales (and vice versa) * Add bidirectionality safeguards in NotificationHelper * Cache bidirectionality formatter instance in NotificationsAdapter
This commit is contained in:
parent
e79b47552e
commit
3a8d96346b
2 changed files with 35 additions and 18 deletions
|
@ -24,6 +24,7 @@ import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.support.v4.text.BidiFormatter;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
@ -65,6 +66,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
private NotificationActionListener notificationActionListener;
|
private NotificationActionListener notificationActionListener;
|
||||||
private FooterViewHolder.State footerState;
|
private FooterViewHolder.State footerState;
|
||||||
private boolean mediaPreviewEnabled;
|
private boolean mediaPreviewEnabled;
|
||||||
|
private BidiFormatter bidiFormatter;
|
||||||
|
|
||||||
public NotificationsAdapter(StatusActionListener statusListener,
|
public NotificationsAdapter(StatusActionListener statusListener,
|
||||||
NotificationActionListener notificationActionListener) {
|
NotificationActionListener notificationActionListener) {
|
||||||
|
@ -74,6 +76,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
this.notificationActionListener = notificationActionListener;
|
this.notificationActionListener = notificationActionListener;
|
||||||
footerState = FooterViewHolder.State.END;
|
footerState = FooterViewHolder.State.END;
|
||||||
mediaPreviewEnabled = true;
|
mediaPreviewEnabled = true;
|
||||||
|
bidiFormatter = BidiFormatter.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -148,7 +151,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
concreteNotificaton.getAccount().getAvatar());
|
concreteNotificaton.getAccount().getAvatar());
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.setMessage(concreteNotificaton, statusListener);
|
holder.setMessage(concreteNotificaton, statusListener, bidiFormatter);
|
||||||
holder.setupButtons(notificationActionListener,
|
holder.setupButtons(notificationActionListener,
|
||||||
concreteNotificaton.getAccount().getId(),
|
concreteNotificaton.getAccount().getId(),
|
||||||
concreteNotificaton.getId());
|
concreteNotificaton.getId());
|
||||||
|
@ -157,7 +160,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
case FOLLOW: {
|
case FOLLOW: {
|
||||||
FollowViewHolder holder = (FollowViewHolder) viewHolder;
|
FollowViewHolder holder = (FollowViewHolder) viewHolder;
|
||||||
holder.setMessage(concreteNotificaton.getAccount().getName(),
|
holder.setMessage(concreteNotificaton.getAccount().getName(),
|
||||||
concreteNotificaton.getAccount().getUsername(), concreteNotificaton.getAccount().getAvatar());
|
concreteNotificaton.getAccount().getUsername(), concreteNotificaton.getAccount().getAvatar(), bidiFormatter);
|
||||||
holder.setupButtons(notificationActionListener, concreteNotificaton.getAccount().getId());
|
holder.setupButtons(notificationActionListener, concreteNotificaton.getAccount().getId());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -265,18 +268,19 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
message.setCompoundDrawablesWithIntrinsicBounds(followIcon, null, null, null);
|
message.setCompoundDrawablesWithIntrinsicBounds(followIcon, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setMessage(String displayName, String username, String avatarUrl) {
|
void setMessage(String displayName, String username, String avatarUrl, BidiFormatter bidiFormatter) {
|
||||||
Context context = message.getContext();
|
Context context = message.getContext();
|
||||||
|
|
||||||
String format = context.getString(R.string.notification_follow_format);
|
String format = context.getString(R.string.notification_follow_format);
|
||||||
String wholeMessage = String.format(format, displayName);
|
String wrappedDisplayName = bidiFormatter.unicodeWrap(displayName);
|
||||||
|
String wholeMessage = String.format(format, wrappedDisplayName);
|
||||||
message.setText(wholeMessage);
|
message.setText(wholeMessage);
|
||||||
|
|
||||||
format = context.getString(R.string.status_username_format);
|
format = context.getString(R.string.status_username_format);
|
||||||
String wholeUsername = String.format(format, username);
|
String wholeUsername = String.format(format, username);
|
||||||
usernameView.setText(wholeUsername);
|
usernameView.setText(wholeUsername);
|
||||||
|
|
||||||
displayNameView.setText(displayName);
|
displayNameView.setText(wrappedDisplayName);
|
||||||
|
|
||||||
if (TextUtils.isEmpty(avatarUrl)) {
|
if (TextUtils.isEmpty(avatarUrl)) {
|
||||||
avatar.setImageResource(R.drawable.avatar_default);
|
avatar.setImageResource(R.drawable.avatar_default);
|
||||||
|
@ -381,10 +385,10 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
timestampInfo.setContentDescription(readoutAloud);
|
timestampInfo.setContentDescription(readoutAloud);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setMessage(NotificationViewData.Concrete notificationViewData, LinkListener listener) {
|
void setMessage(NotificationViewData.Concrete notificationViewData, LinkListener listener, BidiFormatter bidiFormatter) {
|
||||||
this.statusViewData = notificationViewData.getStatusViewData();
|
this.statusViewData = notificationViewData.getStatusViewData();
|
||||||
|
|
||||||
String displayName = notificationViewData.getAccount().getName();
|
String displayName = bidiFormatter.unicodeWrap(notificationViewData.getAccount().getName());
|
||||||
Notification.Type type = notificationViewData.getType();
|
Notification.Type type = notificationViewData.getType();
|
||||||
|
|
||||||
Context context = message.getContext();
|
Context context = message.getContext();
|
||||||
|
|
|
@ -33,6 +33,7 @@ import android.support.v4.app.NotificationManagerCompat;
|
||||||
import android.support.v4.app.RemoteInput;
|
import android.support.v4.app.RemoteInput;
|
||||||
import android.support.v4.app.TaskStackBuilder;
|
import android.support.v4.app.TaskStackBuilder;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v4.text.BidiFormatter;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.keylesspalace.tusky.BuildConfig;
|
import com.keylesspalace.tusky.BuildConfig;
|
||||||
|
@ -119,6 +120,7 @@ public class NotificationHelper {
|
||||||
|
|
||||||
String rawCurrentNotifications = account.getActiveNotifications();
|
String rawCurrentNotifications = account.getActiveNotifications();
|
||||||
JSONArray currentNotifications;
|
JSONArray currentNotifications;
|
||||||
|
BidiFormatter bidiFormatter = BidiFormatter.getInstance();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
currentNotifications = new JSONArray(rawCurrentNotifications);
|
currentNotifications = new JSONArray(rawCurrentNotifications);
|
||||||
|
@ -147,7 +149,7 @@ public class NotificationHelper {
|
||||||
|
|
||||||
notificationId++;
|
notificationId++;
|
||||||
|
|
||||||
builder.setContentTitle(titleForType(context, body))
|
builder.setContentTitle(titleForType(context, body, bidiFormatter))
|
||||||
.setContentText(bodyForType(body));
|
.setContentText(bodyForType(body));
|
||||||
|
|
||||||
if (body.getType() == Notification.Type.MENTION) {
|
if (body.getType() == Notification.Type.MENTION) {
|
||||||
|
@ -208,7 +210,7 @@ public class NotificationHelper {
|
||||||
if (currentNotifications.length() != 1) {
|
if (currentNotifications.length() != 1) {
|
||||||
try {
|
try {
|
||||||
String title = context.getString(R.string.notification_title_summary, currentNotifications.length());
|
String title = context.getString(R.string.notification_title_summary, currentNotifications.length());
|
||||||
String text = joinNames(context, currentNotifications);
|
String text = joinNames(context, currentNotifications, bidiFormatter);
|
||||||
summaryBuilder.setContentTitle(title)
|
summaryBuilder.setContentTitle(title)
|
||||||
.setContentText(text);
|
.setContentText(text);
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
|
@ -491,38 +493,49 @@ public class NotificationHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String wrapItemAt(JSONArray array, int index, BidiFormatter bidiFormatter) throws JSONException {
|
||||||
|
return bidiFormatter.unicodeWrap(array.get(index).toString());
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String joinNames(Context context, JSONArray array) throws JSONException {
|
private static String joinNames(Context context, JSONArray array, BidiFormatter bidiFormatter) throws JSONException {
|
||||||
if (array.length() > 3) {
|
if (array.length() > 3) {
|
||||||
int length = array.length();
|
int length = array.length();
|
||||||
return String.format(context.getString(R.string.notification_summary_large),
|
return String.format(context.getString(R.string.notification_summary_large),
|
||||||
array.get(length - 1), array.get(length - 2), array.get(length - 3), length - 3);
|
wrapItemAt(array, length - 1, bidiFormatter),
|
||||||
|
wrapItemAt(array, length - 2, bidiFormatter),
|
||||||
|
wrapItemAt(array, length - 3, bidiFormatter),
|
||||||
|
length - 3);
|
||||||
} else if (array.length() == 3) {
|
} else if (array.length() == 3) {
|
||||||
return String.format(context.getString(R.string.notification_summary_medium),
|
return String.format(context.getString(R.string.notification_summary_medium),
|
||||||
array.get(2), array.get(1), array.get(0));
|
wrapItemAt(array, 2, bidiFormatter),
|
||||||
|
wrapItemAt(array, 1, bidiFormatter),
|
||||||
|
wrapItemAt(array, 0, bidiFormatter));
|
||||||
} else if (array.length() == 2) {
|
} else if (array.length() == 2) {
|
||||||
return String.format(context.getString(R.string.notification_summary_small),
|
return String.format(context.getString(R.string.notification_summary_small),
|
||||||
array.get(1), array.get(0));
|
wrapItemAt(array, 1, bidiFormatter),
|
||||||
|
wrapItemAt(array, 0, bidiFormatter));
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String titleForType(Context context, Notification notification) {
|
private static String titleForType(Context context, Notification notification, BidiFormatter bidiFormatter) {
|
||||||
|
String accountName = bidiFormatter.unicodeWrap(notification.getAccount().getName());
|
||||||
switch (notification.getType()) {
|
switch (notification.getType()) {
|
||||||
case MENTION:
|
case MENTION:
|
||||||
return String.format(context.getString(R.string.notification_mention_format),
|
return String.format(context.getString(R.string.notification_mention_format),
|
||||||
notification.getAccount().getName());
|
accountName);
|
||||||
case FOLLOW:
|
case FOLLOW:
|
||||||
return String.format(context.getString(R.string.notification_follow_format),
|
return String.format(context.getString(R.string.notification_follow_format),
|
||||||
notification.getAccount().getName());
|
accountName);
|
||||||
case FAVOURITE:
|
case FAVOURITE:
|
||||||
return String.format(context.getString(R.string.notification_favourite_format),
|
return String.format(context.getString(R.string.notification_favourite_format),
|
||||||
notification.getAccount().getName());
|
accountName);
|
||||||
case REBLOG:
|
case REBLOG:
|
||||||
return String.format(context.getString(R.string.notification_reblog_format),
|
return String.format(context.getString(R.string.notification_reblog_format),
|
||||||
notification.getAccount().getName());
|
accountName);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue