From 2154f6580f2a7a634f0a6a1f5c98ac80136d70ba Mon Sep 17 00:00:00 2001 From: Levi Bard Date: Thu, 24 May 2018 19:00:17 +0200 Subject: [PATCH] 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 --- .../tusky/adapter/NotificationsAdapter.java | 18 ++++++---- .../tusky/util/NotificationHelper.java | 35 +++++++++++++------ 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java index 3f727c8f..452e4cf1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -24,6 +24,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; +import android.support.v4.text.BidiFormatter; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; @@ -65,6 +66,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { private NotificationActionListener notificationActionListener; private FooterViewHolder.State footerState; private boolean mediaPreviewEnabled; + private BidiFormatter bidiFormatter; public NotificationsAdapter(StatusActionListener statusListener, NotificationActionListener notificationActionListener) { @@ -74,6 +76,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { this.notificationActionListener = notificationActionListener; footerState = FooterViewHolder.State.END; mediaPreviewEnabled = true; + bidiFormatter = BidiFormatter.getInstance(); } @NonNull @@ -148,7 +151,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { concreteNotificaton.getAccount().getAvatar()); } - holder.setMessage(concreteNotificaton, statusListener); + holder.setMessage(concreteNotificaton, statusListener, bidiFormatter); holder.setupButtons(notificationActionListener, concreteNotificaton.getAccount().getId(), concreteNotificaton.getId()); @@ -157,7 +160,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { case FOLLOW: { FollowViewHolder holder = (FollowViewHolder) viewHolder; holder.setMessage(concreteNotificaton.getAccount().getName(), - concreteNotificaton.getAccount().getUsername(), concreteNotificaton.getAccount().getAvatar()); + concreteNotificaton.getAccount().getUsername(), concreteNotificaton.getAccount().getAvatar(), bidiFormatter); holder.setupButtons(notificationActionListener, concreteNotificaton.getAccount().getId()); break; } @@ -265,18 +268,19 @@ public class NotificationsAdapter extends RecyclerView.Adapter { 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(); 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); format = context.getString(R.string.status_username_format); String wholeUsername = String.format(format, username); usernameView.setText(wholeUsername); - displayNameView.setText(displayName); + displayNameView.setText(wrappedDisplayName); if (TextUtils.isEmpty(avatarUrl)) { avatar.setImageResource(R.drawable.avatar_default); @@ -381,10 +385,10 @@ public class NotificationsAdapter extends RecyclerView.Adapter { timestampInfo.setContentDescription(readoutAloud); } - void setMessage(NotificationViewData.Concrete notificationViewData, LinkListener listener) { + void setMessage(NotificationViewData.Concrete notificationViewData, LinkListener listener, BidiFormatter bidiFormatter) { this.statusViewData = notificationViewData.getStatusViewData(); - String displayName = notificationViewData.getAccount().getName(); + String displayName = bidiFormatter.unicodeWrap(notificationViewData.getAccount().getName()); Notification.Type type = notificationViewData.getType(); Context context = message.getContext(); diff --git a/app/src/main/java/com/keylesspalace/tusky/util/NotificationHelper.java b/app/src/main/java/com/keylesspalace/tusky/util/NotificationHelper.java index a0b9b39a..ae986192 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/NotificationHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/NotificationHelper.java @@ -33,6 +33,7 @@ import android.support.v4.app.NotificationManagerCompat; import android.support.v4.app.RemoteInput; import android.support.v4.app.TaskStackBuilder; import android.support.v4.content.ContextCompat; +import android.support.v4.text.BidiFormatter; import android.util.Log; import com.keylesspalace.tusky.BuildConfig; @@ -119,6 +120,7 @@ public class NotificationHelper { String rawCurrentNotifications = account.getActiveNotifications(); JSONArray currentNotifications; + BidiFormatter bidiFormatter = BidiFormatter.getInstance(); try { currentNotifications = new JSONArray(rawCurrentNotifications); @@ -147,7 +149,7 @@ public class NotificationHelper { notificationId++; - builder.setContentTitle(titleForType(context, body)) + builder.setContentTitle(titleForType(context, body, bidiFormatter)) .setContentText(bodyForType(body)); if (body.getType() == Notification.Type.MENTION) { @@ -208,7 +210,7 @@ public class NotificationHelper { if (currentNotifications.length() != 1) { try { 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) .setContentText(text); } 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 - 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) { int length = array.length(); 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) { 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) { 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; } @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()) { case MENTION: return String.format(context.getString(R.string.notification_mention_format), - notification.getAccount().getName()); + accountName); case FOLLOW: return String.format(context.getString(R.string.notification_follow_format), - notification.getAccount().getName()); + accountName); case FAVOURITE: return String.format(context.getString(R.string.notification_favourite_format), - notification.getAccount().getName()); + accountName); case REBLOG: return String.format(context.getString(R.string.notification_reblog_format), - notification.getAccount().getName()); + accountName); } return null; }