From 958087044591b9322165e6addf7c8f2b167c4acf Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 6 Feb 2021 10:14:51 +0300 Subject: [PATCH] Animated emoji support (#2064) * Animated emoji support * Try to query preference only once * Revert to using SpannableStringBuilder --- .../keylesspalace/tusky/AccountActivity.kt | 11 ++++-- .../tusky/AccountsInListFragment.kt | 9 +++-- .../com/keylesspalace/tusky/MainActivity.kt | 3 +- .../tusky/adapter/AccountAdapter.java | 6 ++- .../tusky/adapter/AccountFieldAdapter.kt | 6 +-- .../tusky/adapter/AccountSelectionAdapter.kt | 9 +++-- .../tusky/adapter/AccountViewHolder.java | 6 +-- .../tusky/adapter/BlocksAdapter.java | 13 +++---- .../tusky/adapter/FollowAdapter.java | 6 +-- .../tusky/adapter/FollowRequestViewHolder.kt | 17 ++++----- .../tusky/adapter/FollowRequestsAdapter.java | 6 +-- .../tusky/adapter/MutesAdapter.java | 13 +++---- .../tusky/adapter/NotificationsAdapter.java | 31 ++++++++++----- .../tusky/adapter/PollAdapter.kt | 11 ++++-- .../tusky/adapter/StatusBaseViewHolder.java | 34 +++++++++++++---- .../tusky/adapter/StatusViewHolder.java | 10 +++-- .../tusky/adapter/TimelineAdapter.java | 3 +- .../announcements/AnnouncementAdapter.kt | 6 ++- .../announcements/AnnouncementsActivity.kt | 3 +- .../components/compose/ComposeActivity.kt | 12 ++++-- .../compose/ComposeAutoCompleteAdapter.java | 11 +++--- .../conversation/ConversationViewHolder.java | 2 +- .../conversation/ConversationsFragment.kt | 4 +- .../preference/PreferencesFragment.kt | 7 ++++ .../report/adapter/StatusViewHolder.kt | 6 +-- .../fragments/ReportStatusesFragment.kt | 3 +- .../search/adapter/SearchAccountsAdapter.kt | 4 +- .../fragments/SearchAccountsFragment.kt | 13 ++++++- .../fragments/SearchStatusesFragment.kt | 3 +- .../tusky/fragment/AccountListFragment.kt | 14 +++++-- .../tusky/fragment/NotificationsFragment.java | 3 +- .../tusky/fragment/TimelineFragment.java | 3 +- .../tusky/fragment/ViewThreadFragment.java | 3 +- .../tusky/settings/SettingsConstants.kt | 1 + .../tusky/util/CustomEmojiHelper.kt | 38 ++++++++++++++----- .../tusky/util/StatusDisplayOptions.kt | 4 +- .../tusky/util/StatusViewHelper.kt | 10 ++--- app/src/main/res/values/strings.xml | 1 + 38 files changed, 225 insertions(+), 120 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index bbcfad80..95101708 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -78,7 +78,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private val viewModel: AccountViewModel by viewModels { viewModelFactory } - private val accountFieldAdapter = AccountFieldAdapter(this) + private lateinit var accountFieldAdapter : AccountFieldAdapter private var followState: FollowState = FollowState.NOT_FOLLOWING private var blocking: Boolean = false @@ -89,6 +89,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private var loadedAccount: Account? = null private var animateAvatar: Boolean = false + private var animateEmojis: Boolean = false // fields for scroll animation private var hideFab: Boolean = false @@ -124,6 +125,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this) animateAvatar = sharedPrefs.getBoolean("animateGifAvatars", false) + animateEmojis = sharedPrefs.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) hideFab = sharedPrefs.getBoolean("fabHide", false) setupToolbar() @@ -162,6 +164,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI accountFollowsYouTextView.hide() // setup the RecyclerView for the account fields + accountFieldAdapter = AccountFieldAdapter(this, animateEmojis) accountFieldList.isNestedScrollingEnabled = false accountFieldList.layoutManager = LinearLayoutManager(this) accountFieldList.adapter = accountFieldAdapter @@ -375,9 +378,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI val usernameFormatted = getString(R.string.status_username_format, account.username) accountUsernameTextView.text = usernameFormatted - accountDisplayNameTextView.text = account.name.emojify(account.emojis, accountDisplayNameTextView) + accountDisplayNameTextView.text = account.name.emojify(account.emojis, accountDisplayNameTextView, animateEmojis) - val emojifiedNote = account.note.emojify(account.emojis, accountNoteTextView) + val emojifiedNote = account.note.emojify(account.emojis, accountNoteTextView, animateEmojis) LinkHelper.setClickableText(accountNoteTextView, emojifiedNote, null, this) // accountFieldAdapter.fields = account.fields ?: emptyList() @@ -437,7 +440,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private fun updateToolbar() { loadedAccount?.let { account -> - val emojifiedName = account.name.emojify(account.emojis, accountToolbar) + val emojifiedName = account.name.emojify(account.emojis, accountToolbar, animateEmojis) try { supportActionBar?.title = EmojiCompat.get().process(emojifiedName) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt index 2933d689..f1c3d54d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt @@ -31,6 +31,7 @@ import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.entity.Account +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.* import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel import com.keylesspalace.tusky.viewmodel.State @@ -71,7 +72,9 @@ class AccountsInListFragment : DialogFragment(), Injectable { private val searchAdapter = SearchAdapter() private val radius by lazy { resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp) } - private val animateAvatar by lazy { PreferenceManager.getDefaultSharedPreferences(requireContext()).getBoolean("animateGifAvatars", false) } + private val pm by lazy { PreferenceManager.getDefaultSharedPreferences(requireContext()) } + private val animateAvatar by lazy { pm.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false) } + private val animateEmojis by lazy { pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -209,7 +212,7 @@ class AccountsInListFragment : DialogFragment(), Injectable { } fun bind(account: Account) { - displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView) + displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView, animateEmojis) usernameTextView.text = account.username loadAvatar(account.avatar, avatar, radius, animateAvatar) } @@ -252,7 +255,7 @@ class AccountsInListFragment : DialogFragment(), Injectable { override val containerView = itemView fun bind(account: Account, inAList: Boolean) { - displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView) + displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView, animateEmojis) usernameTextView.text = account.username loadAvatar(account.avatar, avatar, radius, animateAvatar) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 231c6f43..3f41ca00 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -723,8 +723,9 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } private fun updateProfiles() { + val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) val profiles: MutableList = accountManager.getAllAccountsOrderedByActive().map { acc -> - val emojifiedName = EmojiCompat.get().process(acc.displayName.emojify(acc.emojis, header)) + val emojifiedName = EmojiCompat.get().process(acc.displayName.emojify(acc.emojis, header, animateEmojis)) ProfileDrawerItem().apply { isSelected = acc.isActive diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java index 5c52e39e..24430dce 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java @@ -33,10 +33,14 @@ public abstract class AccountAdapter extends RecyclerView.Adapter { List accountList; AccountActionListener accountActionListener; private boolean bottomLoading; + protected final boolean animateEmojis; + protected final boolean animateAvatar; - AccountAdapter(AccountActionListener accountActionListener) { + AccountAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { this.accountList = new ArrayList<>(); this.accountActionListener = accountActionListener; + this.animateAvatar = animateAvatar; + this.animateEmojis = animateEmojis; bottomLoading = false; } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt index e80129c6..e395a7e6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt @@ -29,7 +29,7 @@ import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.util.* import kotlinx.android.synthetic.main.item_account_field.view.* -class AccountFieldAdapter(private val linkListener: LinkListener) : RecyclerView.Adapter() { +class AccountFieldAdapter(private val linkListener: LinkListener, private val animateEmojis: Boolean) : RecyclerView.Adapter() { var emojis: List = emptyList() var fields: List> = emptyList() @@ -55,10 +55,10 @@ class AccountFieldAdapter(private val linkListener: LinkListener) : RecyclerView viewHolder.valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0) } else { val field = proofOrField.asRight() - val emojifiedName = field.name.emojify(emojis, viewHolder.nameTextView) + val emojifiedName = field.name.emojify(emojis, viewHolder.nameTextView, animateEmojis) viewHolder.nameTextView.text = emojifiedName - val emojifiedValue = field.value.emojify(emojis, viewHolder.valueTextView) + val emojifiedValue = field.value.emojify(emojis, viewHolder.valueTextView, animateEmojis) LinkHelper.setClickableText(viewHolder.valueTextView, emojifiedValue, null, linkListener) if(field.verifiedAt != null) { diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt index dae0db4b..c8df79f9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt @@ -23,6 +23,7 @@ import android.widget.ArrayAdapter import androidx.preference.PreferenceManager import com.keylesspalace.tusky.R import com.keylesspalace.tusky.db.AccountEntity +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.* import kotlinx.android.synthetic.main.item_autocomplete_account.view.* @@ -41,12 +42,14 @@ class AccountSelectionAdapter(context: Context) : ArrayAdapter(co val username = view.username val displayName = view.display_name val avatar = view.avatar + val pm = PreferenceManager.getDefaultSharedPreferences(avatar.context) + val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) + username.text = account.fullName - displayName.text = account.displayName.emojify(account.emojis, displayName) + displayName.text = account.displayName.emojify(account.emojis, displayName, animateEmojis) val avatarRadius = avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_42dp) - val animateAvatar = PreferenceManager.getDefaultSharedPreferences(avatar.context) - .getBoolean("animateGifAvatars", false) + val animateAvatar = pm.getBoolean("animateGifAvatars", false) loadAvatar(account.profilePictureUrl, avatar, avatarRadius, animateAvatar) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java index 7b07d5bd..559426e3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java @@ -22,7 +22,6 @@ public class AccountViewHolder extends RecyclerView.ViewHolder { private ImageView avatarInset; private String accountId; private boolean showBotOverlay; - private boolean animateAvatar; public AccountViewHolder(View itemView) { super(itemView); @@ -32,15 +31,14 @@ public class AccountViewHolder extends RecyclerView.ViewHolder { avatarInset = itemView.findViewById(R.id.account_avatar_inset); SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(itemView.getContext()); showBotOverlay = sharedPrefs.getBoolean("showBotOverlay", true); - animateAvatar = sharedPrefs.getBoolean("animateGifAvatars", false); } - public void setupWithAccount(Account account) { + public void setupWithAccount(Account account, boolean animateAvatar, boolean animateEmojis) { accountId = account.getId(); String format = username.getContext().getString(R.string.status_username_format); String formattedUsername = String.format(format, account.getUsername()); username.setText(formattedUsername); - CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName); + CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName, animateEmojis); displayName.setText(emojifiedName); int avatarRadius = avatar.getContext().getResources() .getDimensionPixelSize(R.dimen.avatar_radius_48dp); diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java index 073d76da..13144cb8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java @@ -34,8 +34,8 @@ import com.keylesspalace.tusky.util.ImageLoadingHelper; public class BlocksAdapter extends AccountAdapter { - public BlocksAdapter(AccountActionListener accountActionListener) { - super(accountActionListener); + public BlocksAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { + super(accountActionListener, animateAvatar, animateEmojis); } @NonNull @@ -60,7 +60,7 @@ public class BlocksAdapter extends AccountAdapter { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { BlockedUserViewHolder holder = (BlockedUserViewHolder) viewHolder; - holder.setupWithAccount(accountList.get(position)); + holder.setupWithAccount(accountList.get(position), animateAvatar, animateEmojis); holder.setupActionListener(accountActionListener); } } @@ -71,7 +71,6 @@ public class BlocksAdapter extends AccountAdapter { private TextView displayName; private ImageButton unblock; private String id; - private boolean animateAvatar; BlockedUserViewHolder(View itemView) { super(itemView); @@ -79,14 +78,12 @@ public class BlocksAdapter extends AccountAdapter { username = itemView.findViewById(R.id.blocked_user_username); displayName = itemView.findViewById(R.id.blocked_user_display_name); unblock = itemView.findViewById(R.id.blocked_user_unblock); - animateAvatar = PreferenceManager.getDefaultSharedPreferences(itemView.getContext()) - .getBoolean("animateGifAvatars", false); } - void setupWithAccount(Account account) { + void setupWithAccount(Account account, boolean animateAvatar, boolean animateEmojis) { id = account.getId(); - CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName); + CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName, animateEmojis); displayName.setText(emojifiedName); String format = username.getContext().getString(R.string.status_username_format); String formattedUsername = String.format(format, account.getUsername()); diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java index 82158746..98cb9e4d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java @@ -27,8 +27,8 @@ import com.keylesspalace.tusky.interfaces.AccountActionListener; /** Both for follows and following lists. */ public class FollowAdapter extends AccountAdapter { - public FollowAdapter(AccountActionListener accountActionListener) { - super(accountActionListener); + public FollowAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { + super(accountActionListener, animateAvatar, animateEmojis); } @NonNull @@ -53,7 +53,7 @@ public class FollowAdapter extends AccountAdapter { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { AccountViewHolder holder = (AccountViewHolder) viewHolder; - holder.setupWithAccount(accountList.get(position)); + holder.setupWithAccount(accountList.get(position), animateAvatar, animateEmojis); holder.setupActionListener(accountActionListener); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt index dec4586b..8fa14731 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt @@ -10,27 +10,24 @@ import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.R import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.interfaces.AccountActionListener -import com.keylesspalace.tusky.util.emojify -import com.keylesspalace.tusky.util.loadAvatar -import com.keylesspalace.tusky.util.unicodeWrap -import com.keylesspalace.tusky.util.visible +import com.keylesspalace.tusky.util.* import kotlinx.android.synthetic.main.item_follow_request_notification.view.* -internal class FollowRequestViewHolder(itemView: View, private val showHeader: Boolean) : RecyclerView.ViewHolder(itemView) { +internal class FollowRequestViewHolder( + itemView: View, + private val showHeader: Boolean) : RecyclerView.ViewHolder(itemView) { private var id: String? = null - private val animateAvatar: Boolean = PreferenceManager.getDefaultSharedPreferences(itemView.context) - .getBoolean("animateGifAvatars", false) - fun setupWithAccount(account: Account) { + fun setupWithAccount(account: Account, animateAvatar: Boolean, animateEmojis: Boolean) { id = account.id val wrappedName = account.name.unicodeWrap() - val emojifiedName: CharSequence = wrappedName.emojify(account.emojis, itemView) + val emojifiedName: CharSequence = wrappedName.emojify(account.emojis, itemView, animateEmojis) itemView.displayNameTextView.text = emojifiedName if (showHeader) { val wholeMessage: String = itemView.context.getString(R.string.notification_follow_request_format, wrappedName) itemView.notificationTextView?.text = SpannableStringBuilder(wholeMessage).apply { setSpan(StyleSpan(Typeface.BOLD), 0, wrappedName.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - }.emojify(account.emojis, itemView) + }.emojify(account.emojis, itemView, animateEmojis) } itemView.notificationTextView?.visible(showHeader) val format = itemView.context.getString(R.string.status_username_format) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java index dab3d4fe..9ba59884 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java @@ -27,8 +27,8 @@ import com.keylesspalace.tusky.interfaces.AccountActionListener; public class FollowRequestsAdapter extends AccountAdapter { - public FollowRequestsAdapter(AccountActionListener accountActionListener) { - super(accountActionListener); + public FollowRequestsAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { + super(accountActionListener, animateAvatar, animateEmojis); } @NonNull @@ -53,7 +53,7 @@ public class FollowRequestsAdapter extends AccountAdapter { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder; - holder.setupWithAccount(accountList.get(position)); + holder.setupWithAccount(accountList.get(position), animateAvatar, animateEmojis); holder.setupActionListener(accountActionListener); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java index c4224c9c..e1a30759 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java @@ -23,8 +23,8 @@ import java.util.HashMap; public class MutesAdapter extends AccountAdapter { private HashMap mutingNotificationsMap; - public MutesAdapter(AccountActionListener accountActionListener) { - super(accountActionListener); + public MutesAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { + super(accountActionListener, animateAvatar, animateEmojis); mutingNotificationsMap = new HashMap(); } @@ -51,7 +51,7 @@ public class MutesAdapter extends AccountAdapter { if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { MutedUserViewHolder holder = (MutedUserViewHolder) viewHolder; Account account = accountList.get(position); - holder.setupWithAccount(account, mutingNotificationsMap.get(account.getId())); + holder.setupWithAccount(account, mutingNotificationsMap.get(account.getId()), animateAvatar, animateEmojis); holder.setupActionListener(accountActionListener); } } @@ -73,7 +73,6 @@ public class MutesAdapter extends AccountAdapter { private ImageButton unmute; private ImageButton muteNotifications; private String id; - private boolean animateAvatar; private boolean notifications; MutedUserViewHolder(View itemView) { @@ -83,13 +82,11 @@ public class MutesAdapter extends AccountAdapter { displayName = itemView.findViewById(R.id.muted_user_display_name); unmute = itemView.findViewById(R.id.muted_user_unmute); muteNotifications = itemView.findViewById(R.id.muted_user_mute_notifications); - animateAvatar = PreferenceManager.getDefaultSharedPreferences(itemView.getContext()) - .getBoolean("animateGifAvatars", false); } - void setupWithAccount(Account account, Boolean mutingNotifications) { + void setupWithAccount(Account account, Boolean mutingNotifications, boolean animateAvatar, boolean animateEmojis) { id = account.getId(); - CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName); + CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName, animateEmojis); displayName.setText(emojifiedName); String format = username.getContext().getString(R.string.status_username_format); String formattedUsername = String.format(format, account.getUsername()); 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 2fe95385..833d18f4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -232,7 +232,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { case VIEW_TYPE_FOLLOW_REQUEST: { if (payloadForHolder == null) { FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder; - holder.setupWithAccount(concreteNotificaton.getAccount()); + holder.setupWithAccount(concreteNotificaton.getAccount(), statusDisplayOptions.animateAvatars(), statusDisplayOptions.animateEmojis()); holder.setupActionListener(accountActionListener); } } @@ -255,7 +255,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter { statusDisplayOptions.useBlurhash(), CardViewMode.NONE, statusDisplayOptions.confirmReblogs(), - statusDisplayOptions.hideStats() + statusDisplayOptions.hideStats(), + statusDisplayOptions.animateEmojis() ); } @@ -336,13 +337,17 @@ public class NotificationsAdapter extends RecyclerView.Adapter { String format = context.getString(R.string.notification_follow_format); String wrappedDisplayName = StringUtils.unicodeWrap(account.getName()); String wholeMessage = String.format(format, wrappedDisplayName); - CharSequence emojifiedMessage = CustomEmojiHelper.emojify(wholeMessage, account.getEmojis(), message); + CharSequence emojifiedMessage = CustomEmojiHelper.emojify( + wholeMessage, account.getEmojis(), message, statusDisplayOptions.animateEmojis() + ); message.setText(emojifiedMessage); String username = context.getString(R.string.status_username_format, account.getUsername()); usernameView.setText(username); - CharSequence emojifiedDisplayName = CustomEmojiHelper.emojify(wrappedDisplayName, account.getEmojis(), usernameView); + CharSequence emojifiedDisplayName = CustomEmojiHelper.emojify( + wrappedDisplayName, account.getEmojis(), usernameView, statusDisplayOptions.animateEmojis() + ); displayNameView.setText(emojifiedDisplayName); @@ -425,7 +430,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { } private void setDisplayName(String name, List emojis) { - CharSequence emojifiedName = CustomEmojiHelper.emojify(name, emojis, displayName); + CharSequence emojifiedName = CustomEmojiHelper.emojify(name, emojis, displayName, statusDisplayOptions.animateEmojis()); displayName.setText(emojifiedName); } @@ -519,7 +524,9 @@ public class NotificationsAdapter extends RecyclerView.Adapter { final SpannableStringBuilder str = new SpannableStringBuilder(wholeMessage); str.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - CharSequence emojifiedText = CustomEmojiHelper.emojify(str, notificationViewData.getAccount().getEmojis(), message); + CharSequence emojifiedText = CustomEmojiHelper.emojify( + str, notificationViewData.getAccount().getEmojis(), message, statusDisplayOptions.animateEmojis() + ); message.setText(emojifiedText); if (statusViewData != null) { @@ -630,11 +637,17 @@ public class NotificationsAdapter extends RecyclerView.Adapter { statusContent.setFilters(NO_INPUT_FILTER); } - CharSequence emojifiedText = CustomEmojiHelper.emojify(content, emojis, statusContent); + CharSequence emojifiedText = CustomEmojiHelper.emojify( + content, emojis, statusContent, statusDisplayOptions.animateEmojis() + ); LinkHelper.setClickableText(statusContent, emojifiedText, statusViewData.getMentions(), listener); - CharSequence emojifiedContentWarning = - CustomEmojiHelper.emojify(statusViewData.getSpoilerText(), statusViewData.getStatusEmojis(), contentWarningDescriptionTextView); + CharSequence emojifiedContentWarning = CustomEmojiHelper.emojify( + statusViewData.getSpoilerText(), + statusViewData.getStatusEmojis(), + contentWarningDescriptionTextView, + statusDisplayOptions.animateEmojis() + ); contentWarningDescriptionTextView.setText(emojifiedContentWarning); } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt index a990d326..0208b953 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt @@ -38,6 +38,7 @@ class PollAdapter: RecyclerView.Adapter() { private var mode = RESULT private var emojis: List = emptyList() private var resultClickListener: View.OnClickListener? = null + private var animateEmojis = false fun setup( options: List, @@ -45,13 +46,15 @@ class PollAdapter: RecyclerView.Adapter() { votersCount: Int?, emojis: List, mode: Int, - resultClickListener: View.OnClickListener?) { + resultClickListener: View.OnClickListener?, + animateEmojis: Boolean) { this.pollOptions = options this.voteCount = voteCount this.votersCount = votersCount this.emojis = emojis this.mode = mode this.resultClickListener = resultClickListener + this.animateEmojis = animateEmojis notifyDataSetChanged() } @@ -81,7 +84,7 @@ class PollAdapter: RecyclerView.Adapter() { RESULT -> { val percent = calculatePercent(option.votesCount, votersCount, voteCount) val emojifiedPollOptionText = buildDescription(option.title, percent, holder.resultTextView.context) - .emojify(emojis, holder.resultTextView) + .emojify(emojis, holder.resultTextView, animateEmojis) holder.resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText) val level = percent * 100 @@ -90,7 +93,7 @@ class PollAdapter: RecyclerView.Adapter() { holder.resultTextView.setOnClickListener(resultClickListener) } SINGLE -> { - val emojifiedPollOptionText = option.title.emojify(emojis, holder.radioButton) + val emojifiedPollOptionText = option.title.emojify(emojis, holder.radioButton, animateEmojis) holder.radioButton.text = EmojiCompat.get().process(emojifiedPollOptionText) holder.radioButton.isChecked = option.selected holder.radioButton.setOnClickListener { @@ -101,7 +104,7 @@ class PollAdapter: RecyclerView.Adapter() { } } MULTIPLE -> { - val emojifiedPollOptionText = option.title.emojify(emojis, holder.checkBox) + val emojifiedPollOptionText = option.title.emojify(emojis, holder.checkBox, animateEmojis) holder.checkBox.text = EmojiCompat.get().process(emojifiedPollOptionText) holder.checkBox.isChecked = option.selected holder.checkBox.setOnCheckedChangeListener { _, isChecked -> diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java index 5bce0624..3fc27d7c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -181,8 +181,10 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { protected abstract int getMediaPreviewHeight(Context context); - protected void setDisplayName(String name, List customEmojis) { - CharSequence emojifiedName = CustomEmojiHelper.emojify(name, customEmojis, displayName); + protected void setDisplayName(String name, List customEmojis, StatusDisplayOptions statusDisplayOptions) { + CharSequence emojifiedName = CustomEmojiHelper.emojify( + name, customEmojis, displayName, statusDisplayOptions.animateEmojis() + ); displayName.setText(emojifiedName); } @@ -206,7 +208,9 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { final StatusActionListener listener) { boolean sensitive = !TextUtils.isEmpty(spoilerText); if (sensitive) { - CharSequence emojiSpoiler = CustomEmojiHelper.emojify(spoilerText, emojis, contentWarningDescription); + CharSequence emojiSpoiler = CustomEmojiHelper.emojify( + spoilerText, emojis, contentWarningDescription, statusDisplayOptions.animateEmojis() + ); contentWarningDescription.setText(emojiSpoiler); contentWarningDescription.setVisibility(View.VISIBLE); contentWarningButton.setVisibility(View.VISIBLE); @@ -245,7 +249,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { StatusDisplayOptions statusDisplayOptions, final StatusActionListener listener) { if (expanded) { - CharSequence emojifiedText = CustomEmojiHelper.emojify(content, emojis, this.content); + CharSequence emojifiedText = CustomEmojiHelper.emojify(content, emojis, this.content, statusDisplayOptions.animateEmojis()); LinkHelper.setClickableText(this.content, emojifiedText, mentions, listener); for (int i = 0; i < mediaLabels.length; ++i) { updateMediaLabel(i, sensitive, expanded); @@ -709,7 +713,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { StatusDisplayOptions statusDisplayOptions, @Nullable Object payloads) { if (payloads == null) { - setDisplayName(status.getUserFullName(), status.getAccountEmojis()); + setDisplayName(status.getUserFullName(), status.getAccountEmojis(), statusDisplayOptions); setUsername(status.getNickname()); setCreatedAt(status.getCreatedAt(), statusDisplayOptions); setIsReply(status.getInReplyToId() != null); @@ -927,12 +931,28 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { listener.onViewThread(position); } }; - pollAdapter.setup(poll.getOptions(), poll.getVotesCount(), poll.getVotersCount(), emojis, PollAdapter.RESULT, viewThreadListener); + pollAdapter.setup( + poll.getOptions(), + poll.getVotesCount(), + poll.getVotersCount(), + emojis, + PollAdapter.RESULT, + viewThreadListener, + statusDisplayOptions.animateEmojis() + ); pollButton.setVisibility(View.GONE); } else { // voting possible - pollAdapter.setup(poll.getOptions(), poll.getVotesCount(), poll.getVotersCount(), emojis, poll.getMultiple() ? PollAdapter.MULTIPLE : PollAdapter.SINGLE, null); + pollAdapter.setup( + poll.getOptions(), + poll.getVotesCount(), + poll.getVotersCount(), + emojis, + poll.getMultiple() ? PollAdapter.MULTIPLE : PollAdapter.SINGLE, + null, + statusDisplayOptions.animateEmojis() + ); pollButton.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java index 4a0f6679..043b7b35 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java @@ -66,7 +66,7 @@ public class StatusViewHolder extends StatusBaseViewHolder { if (rebloggedByDisplayName == null) { hideStatusInfo(); } else { - setRebloggedByDisplayName(rebloggedByDisplayName, status); + setRebloggedByDisplayName(rebloggedByDisplayName, status, statusDisplayOptions); statusInfo.setOnClickListener(v -> listener.onOpenReblog(getAdapterPosition())); } @@ -75,11 +75,15 @@ public class StatusViewHolder extends StatusBaseViewHolder { } - private void setRebloggedByDisplayName(final CharSequence name, final StatusViewData.Concrete status) { + private void setRebloggedByDisplayName(final CharSequence name, + final StatusViewData.Concrete status, + final StatusDisplayOptions statusDisplayOptions) { Context context = statusInfo.getContext(); CharSequence wrappedName = StringUtils.unicodeWrap(name); CharSequence boostedText = context.getString(R.string.status_boosted_format, wrappedName); - CharSequence emojifiedText = CustomEmojiHelper.emojify(boostedText, status.getRebloggedByAccountEmojis(), statusInfo); + CharSequence emojifiedText = CustomEmojiHelper.emojify( + boostedText, status.getRebloggedByAccountEmojis(), statusInfo, statusDisplayOptions.animateEmojis() + ); statusInfo.setText(emojifiedText); statusInfo.setVisibility(View.VISIBLE); } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java index 7963847a..4be922d6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java @@ -66,7 +66,8 @@ public final class TimelineAdapter extends RecyclerView.Adapter { statusDisplayOptions.useBlurhash(), statusDisplayOptions.cardViewMode(), statusDisplayOptions.confirmReblogs(), - statusDisplayOptions.hideStats() + statusDisplayOptions.hideStats(), + statusDisplayOptions.animateEmojis() ); } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt index c0e6bdd8..b54b1555 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt @@ -42,7 +42,8 @@ interface AnnouncementActionListener: LinkListener { class AnnouncementAdapter( private var items: List = emptyList(), private val listener: AnnouncementActionListener, - private val wellbeingEnabled: Boolean = false + private val wellbeingEnabled: Boolean = false, + private val animateEmojis: Boolean = false ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnnouncementViewHolder { @@ -99,7 +100,8 @@ class AnnouncementAdapter( reaction.staticUrl ?: "", null )), - this + this, + animateEmojis ) isChecked = reaction.me diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt index 865574a5..ffd97191 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt @@ -82,8 +82,9 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener, val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) - adapter = AnnouncementAdapter(emptyList(), this, wellbeingEnabled) + adapter = AnnouncementAdapter(emptyList(), this, wellbeingEnabled, animateEmojis) announcementsList.adapter = adapter diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index 75b14239..8fc48e9c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -69,6 +69,7 @@ import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Emoji import com.keylesspalace.tusky.entity.NewPoll import com.keylesspalace.tusky.entity.Status +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.* import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial @@ -160,7 +161,7 @@ class ComposeActivity : BaseActivity(), composeScheduleView.setDateTime(composeOptions?.scheduledAt) } - setupComposeField(viewModel.startingText) + setupComposeField(preferences, viewModel.startingText) setupContentWarningField(composeOptions?.contentWarning) setupPollView() applyShareIntent(intent, savedInstanceState) @@ -245,13 +246,18 @@ class ComposeActivity : BaseActivity(), composeContentWarningField.onTextChanged { _, _, _, _ -> updateVisibleCharactersLeft() } } - private fun setupComposeField(startingText: String?) { + private fun setupComposeField(preferences: SharedPreferences, startingText: String?) { composeEditField.setOnCommitContentListener(this) composeEditField.setOnKeyListener { _, keyCode, event -> this.onKeyDown(keyCode, event) } composeEditField.setAdapter( - ComposeAutoCompleteAdapter(this)) + ComposeAutoCompleteAdapter( + this, + preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) + ) + ) composeEditField.setTokenizer(ComposeTokenizer()) composeEditField.setText(startingText) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java index df9ae8a8..b2fa94c3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java @@ -53,11 +53,15 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter private final ArrayList resultList; private final AutocompletionProvider autocompletionProvider; + private final boolean animateAvatar; + private final boolean animateEmojis; - public ComposeAutoCompleteAdapter(AutocompletionProvider autocompletionProvider) { + public ComposeAutoCompleteAdapter(AutocompletionProvider autocompletionProvider, boolean animateAvatar, boolean animateEmojis) { super(); resultList = new ArrayList<>(); this.autocompletionProvider = autocompletionProvider; + this.animateAvatar = animateAvatar; + this.animateEmojis = animateEmojis; } @Override @@ -147,15 +151,12 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter ); accountViewHolder.username.setText(formattedUsername); CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), - account.getEmojis(), accountViewHolder.displayName); + account.getEmojis(), accountViewHolder.displayName, animateEmojis); accountViewHolder.displayName.setText(emojifiedName); int avatarRadius = accountViewHolder.avatar.getContext().getResources() .getDimensionPixelSize(R.dimen.avatar_radius_42dp); - boolean animateAvatar = PreferenceManager.getDefaultSharedPreferences(accountViewHolder.avatar.getContext()) - .getBoolean("animateGifAvatars", false); - ImageLoadingHelper.loadAvatar( account.getAvatar(), accountViewHolder.avatar, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java index 19ef749e..e74be628 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java @@ -75,7 +75,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder { setupCollapsedState(status.getCollapsible(), status.getCollapsed(), status.getExpanded(), status.getSpoilerText(), listener); - setDisplayName(account.getDisplayName(), account.getEmojis()); + setDisplayName(account.getDisplayName(), account.getEmojis(), statusDisplayOptions); setUsername(account.getUsername()); setCreatedAt(status.getCreatedAt(), statusDisplayOptions); setIsReply(status.getInReplyToId() != null); diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt index d50f9043..abae8702 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt @@ -67,8 +67,8 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res useBlurhash = preferences.getBoolean("useBlurhash", true), cardViewMode = CardViewMode.NONE, confirmReblogs = preferences.getBoolean("confirmReblogs", true), - hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) - + hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ) adapter = ConversationAdapter(statusDisplayOptions, this, ::onTopLoaded, viewModel::retry) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt index d2598c12..e0a1b683 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt @@ -174,6 +174,13 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable { setTitle(R.string.pref_title_enable_swipe_for_tabs) isSingleLineTitle = false } + + switchPreference { + setDefaultValue(false) + key = PrefKeys.ANIMATE_CUSTOM_EMOJIS + setTitle(R.string.pref_title_animate_custom_emojis) + isSingleLineTitle = false + } } preferenceCategory(R.string.pref_title_browser_settings) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt index 93b3a7d2..8201de2e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt @@ -75,7 +75,7 @@ class StatusViewHolder( sensitive, previewListener, viewState.isMediaShow(status.id, status.sensitive), mediaViewHeight) - statusViewHelper.setupPollReadonly(status.poll.toViewData(), status.emojis, statusDisplayOptions.useAbsoluteTime) + statusViewHelper.setupPollReadonly(status.poll.toViewData(), status.emojis, statusDisplayOptions) setCreatedAt(status.createdAt) } @@ -89,7 +89,7 @@ class StatusViewHolder( itemView.statusContentWarningButton.hide() itemView.statusContentWarningDescription.hide() } else { - val emojiSpoiler = status.spoilerText.emojify(status.emojis, itemView.statusContentWarningDescription) + val emojiSpoiler = status.spoilerText.emojify(status.emojis, itemView.statusContentWarningDescription, statusDisplayOptions.animateEmojis) itemView.statusContentWarningDescription.text = emojiSpoiler itemView.statusContentWarningDescription.show() itemView.statusContentWarningButton.show() @@ -122,7 +122,7 @@ class StatusViewHolder( emojis: List, listener: LinkListener) { if (expanded) { - val emojifiedText = content.emojify(emojis, itemView.statusContent) + val emojifiedText = content.emojify(emojis, itemView.statusContent, statusDisplayOptions.animateEmojis) LinkHelper.setClickableText(itemView.statusContent, emojifiedText, mentions, listener) } else { LinkHelper.setClickableMentions(itemView.statusContent, mentions, listener) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt index 70bc694d..8ffe243e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt @@ -111,7 +111,8 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje useBlurhash = preferences.getBoolean("useBlurhash", true), cardViewMode = CardViewMode.NONE, confirmReblogs = preferences.getBoolean("confirmReblogs", true), - hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ) adapter = StatusesAdapter(statusDisplayOptions, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt index c135ad7d..b6bc9568 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt @@ -25,7 +25,7 @@ import com.keylesspalace.tusky.adapter.AccountViewHolder import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.interfaces.LinkListener -class SearchAccountsAdapter(private val linkListener: LinkListener) +class SearchAccountsAdapter(private val linkListener: LinkListener, private val animateAvatars: Boolean, private val animateEmojis: Boolean) : PagedListAdapter(ACCOUNT_COMPARATOR) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { @@ -37,7 +37,7 @@ class SearchAccountsAdapter(private val linkListener: LinkListener) override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { getItem(position)?.let { item -> (holder as AccountViewHolder).apply { - setupWithAccount(item) + setupWithAccount(item, animateAvatars, animateEmojis) setupLinkListener(linkListener) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt index 714580f7..c453f97c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt @@ -18,12 +18,23 @@ package com.keylesspalace.tusky.components.search.fragments import androidx.lifecycle.LiveData import androidx.paging.PagedList import androidx.paging.PagedListAdapter +import androidx.preference.PreferenceManager import com.keylesspalace.tusky.components.search.adapter.SearchAccountsAdapter import com.keylesspalace.tusky.entity.Account +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.NetworkState +import kotlinx.android.synthetic.main.fragment_search.* class SearchAccountsFragment : SearchFragment() { - override fun createAdapter(): PagedListAdapter = SearchAccountsAdapter(this) + override fun createAdapter(): PagedListAdapter { + val preferences = PreferenceManager.getDefaultSharedPreferences(searchRecyclerView.context) + + return SearchAccountsAdapter( + this, + preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) + ) + } override val networkStateRefresh: LiveData get() = viewModel.networkStateAccountRefresh diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt index bc7ac2ba..6d96bb5a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt @@ -87,7 +87,8 @@ class SearchStatusesFragment : SearchFragment BlocksAdapter(this) - Type.MUTES -> MutesAdapter(this) - Type.FOLLOW_REQUESTS -> FollowRequestsAdapter(this) - else -> FollowAdapter(this) + Type.BLOCKS -> BlocksAdapter(this, animateAvatar, animateEmojis) + Type.MUTES -> MutesAdapter(this, animateAvatar, animateEmojis) + Type.FOLLOW_REQUESTS -> FollowRequestsAdapter(this, animateAvatar, animateEmojis) + else -> FollowAdapter(this, animateAvatar, animateEmojis) } recyclerView.adapter = adapter diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java index 216ee228..5db3983f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -252,7 +252,8 @@ public class NotificationsFragment extends SFragment implements preferences.getBoolean("useBlurhash", true), CardViewMode.NONE, preferences.getBoolean("confirmReblogs", true), - preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ); adapter = new NotificationsAdapter(accountManager.getActiveAccount().getAccountId(), diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index 06ca56f3..ebaa8be8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -252,7 +252,8 @@ public class TimelineFragment extends SFragment implements CardViewMode.INDENTED : CardViewMode.NONE, preferences.getBoolean("confirmReblogs", true), - preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ); adapter = new TimelineAdapter(dataSource, statusDisplayOptions, this); diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java index 5ce66eee..0c415e1f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java @@ -134,7 +134,8 @@ public final class ViewThreadFragment extends SFragment implements CardViewMode.INDENTED : CardViewMode.NONE, preferences.getBoolean("confirmReblogs", true), - preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ); adapter = new ThreadAdapter(statusDisplayOptions, this); } diff --git a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt index a35885f3..d014ec0c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt +++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt @@ -31,6 +31,7 @@ object PrefKeys { const val SHOW_CARDS_IN_TIMELINES = "showCardsInTimelines" const val CONFIRM_REBLOGS = "confirmReblogs" const val ENABLE_SWIPE_FOR_TABS = "enableSwipeForTabs" + const val ANIMATE_CUSTOM_EMOJIS = "animateCustomEmojis" const val CUSTOM_TABS = "customTabs" const val WELLBEING_LIMITED_NOTIFICATIONS = "wellbeingModeLimitedNotifications" diff --git a/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt index 679f38d3..7521afe4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt @@ -16,11 +16,9 @@ @file:JvmName("CustomEmojiHelper") package com.keylesspalace.tusky.util -import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Paint -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable +import android.graphics.drawable.* import android.text.SpannableStringBuilder import android.text.style.ReplacementSpan import android.view.View @@ -33,6 +31,8 @@ import com.keylesspalace.tusky.entity.Emoji import java.lang.ref.WeakReference import java.util.regex.Pattern +import androidx.preference.PreferenceManager +import com.keylesspalace.tusky.settings.PrefKeys /** * replaces emoji shortcodes in a text with EmojiSpans @@ -41,7 +41,7 @@ import java.util.regex.Pattern * @param view a reference to the a view the emojis will be shown in (should be the TextView, but parents of the TextView are also acceptable) * @return the text with the shortcodes replaced by EmojiSpans */ -fun CharSequence.emojify(emojis: List?, view: View) : CharSequence { +fun CharSequence.emojify(emojis: List?, view: View, animate: Boolean) : CharSequence { if(emojis.isNullOrEmpty()) return this @@ -56,9 +56,9 @@ fun CharSequence.emojify(emojis: List?, view: View) : CharSequence { builder.setSpan(span, matcher.start(), matcher.end(), 0) Glide.with(view) - .asBitmap() + .asDrawable() .load(url) - .into(span.getTarget()) + .into(span.getTarget(animate)) } } return builder @@ -97,11 +97,29 @@ class EmojiSpan(val viewWeakReference: WeakReference) : ReplacementSpan() } } - fun getTarget(): Target { - return object : CustomTarget() { - override fun onResourceReady(resource: Bitmap, transition: Transition?) { + fun getTarget(animate : Boolean): Target { + return object : CustomTarget() { + override fun onResourceReady(resource: Drawable, transition: Transition?) { viewWeakReference.get()?.let { view -> - imageDrawable = BitmapDrawable(view.context.resources, resource) + if(animate && resource is Animatable) { + val callback = resource.callback + + resource.callback = object: Drawable.Callback { + override fun unscheduleDrawable(p0: Drawable, p1: Runnable) { + callback?.unscheduleDrawable(p0, p1) + } + override fun scheduleDrawable(p0: Drawable, p1: Runnable, p2: Long) { + callback?.scheduleDrawable(p0, p1, p2) + } + override fun invalidateDrawable(p0: Drawable) { + callback?.invalidateDrawable(p0) + view.invalidate() + } + } + resource.start() + } + + imageDrawable = resource view.invalidate() } } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt index 93cbb3d9..ce19e00e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt @@ -16,5 +16,7 @@ data class StatusDisplayOptions( @get:JvmName("confirmReblogs") val confirmReblogs: Boolean, @get:JvmName("hideStats") - val hideStats: Boolean + val hideStats: Boolean, + @get:JvmName("animateEmojis") + val animateEmojis: Boolean ) \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt index db0441bd..82210029 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt @@ -243,7 +243,7 @@ class StatusViewHelper(private val itemView: View) { } } - fun setupPollReadonly(poll: PollViewData?, emojis: List, useAbsoluteTime: Boolean) { + fun setupPollReadonly(poll: PollViewData?, emojis: List, statusDisplayOptions: StatusDisplayOptions) { val pollResults = listOf( itemView.findViewById(R.id.status_poll_option_result_0), itemView.findViewById(R.id.status_poll_option_result_1), @@ -261,10 +261,10 @@ class StatusViewHelper(private val itemView: View) { val timestamp = System.currentTimeMillis() - setupPollResult(poll, emojis, pollResults) + setupPollResult(poll, emojis, pollResults, statusDisplayOptions.animateEmojis) pollDescription.visibility = View.VISIBLE - pollDescription.text = getPollInfoText(timestamp, poll, pollDescription, useAbsoluteTime) + pollDescription.text = getPollInfoText(timestamp, poll, pollDescription, statusDisplayOptions.useAbsoluteTime) } } @@ -292,7 +292,7 @@ class StatusViewHelper(private val itemView: View) { } - private fun setupPollResult(poll: PollViewData, emojis: List, pollResults: List) { + private fun setupPollResult(poll: PollViewData, emojis: List, pollResults: List, animateEmojis: Boolean) { val options = poll.options for (i in 0 until Status.MAX_POLL_OPTIONS) { @@ -300,7 +300,7 @@ class StatusViewHelper(private val itemView: View) { val percent = calculatePercent(options[i].votesCount, poll.votersCount, poll.votesCount) val pollOptionText = buildDescription(options[i].title, percent, pollResults[i].context) - pollResults[i].text = pollOptionText.emojify(emojis, pollResults[i]) + pollResults[i].text = pollOptionText.emojify(emojis, pollResults[i], animateEmojis) pollResults[i].visibility = View.VISIBLE val level = percent * 100 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c1615e04..6aa4a3a8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -243,6 +243,7 @@ Show indicator for bots Animate GIF avatars Show colorful gradients for hidden media + Animate custom emojis Timeline filtering Tabs