From 759fd0f11b134412d579f52fda819cf4e1600a0d Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Mon, 30 Jul 2018 15:43:27 +0200 Subject: [PATCH] Custom emojis in drawer (#737) * upgrade MaterialDrawer * improve CustomEmojiHelper so now any parent view can be used for invalidation * cleanup MainActivity a bit * add emojiList to account database and show compatEmojis and custom emojis in drawer * improve perf of drawer profile update * fix account switching * reuse gson, break after profile item was found --- app/build.gradle | 2 +- .../com/keylesspalace/tusky/MainActivity.java | 58 ++++++++++--------- .../keylesspalace/tusky/TuskyApplication.java | 3 +- .../keylesspalace/tusky/db/AccountEntity.kt | 7 ++- .../keylesspalace/tusky/db/AccountManager.kt | 1 + .../keylesspalace/tusky/db/AppDatabase.java | 11 +++- .../com/keylesspalace/tusky/db/Converters.kt | 36 ++++++++++++ .../keylesspalace/tusky/db/InstanceEntity.kt | 17 ------ .../tusky/util/CustomEmojiHelper.java | 28 ++++----- 9 files changed, 99 insertions(+), 64 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/db/Converters.kt diff --git a/app/build.gradle b/app/build.gradle index 91fb8a8b..5340b801 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -65,7 +65,7 @@ ext.daggerVersion = '2.16' // if libraries are changed here, they should also be changed in LicenseActivity dependencies { - implementation('com.mikepenz:materialdrawer:6.0.7@aar') { + implementation('com.mikepenz:materialdrawer:6.0.9@aar') { transitive = true } implementation "com.android.support:appcompat-v7:$supportLibraryVersion" diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java index 673fac02..c01d0348 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java @@ -26,6 +26,7 @@ import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.TabLayout; import android.support.graphics.drawable.VectorDrawableCompat; +import android.support.text.emoji.EmojiCompat; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.support.v4.view.ViewPager; @@ -39,6 +40,7 @@ import com.keylesspalace.tusky.db.AccountEntity; import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.interfaces.ActionButtonActivity; import com.keylesspalace.tusky.pager.TimelinePagerAdapter; +import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.keylesspalace.tusky.util.NotificationHelper; import com.keylesspalace.tusky.util.ThemeUtils; import com.mikepenz.google_material_typeface_library.GoogleMaterial; @@ -71,6 +73,7 @@ import retrofit2.Response; public final class MainActivity extends BottomSheetActivity implements ActionButtonActivity, HasSupportFragmentInjector { + private static final String TAG = "MainActivity"; // logging tag private static final long DRAWER_ITEM_ADD_ACCOUNT = -13; private static final long DRAWER_ITEM_EDIT_PROFILE = 0; @@ -88,8 +91,6 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut @Inject public DispatchingAndroidInjector fragmentInjector; - private static int COMPOSE_RESULT = 1; - private FloatingActionButton composeButton; private AccountHeader headerResult; private Drawer drawer; @@ -125,7 +126,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut floatingBtn.setOnClickListener(v -> { Intent composeIntent = new Intent(getApplicationContext(), ComposeActivity.class); - startActivityForResult(composeIntent, COMPOSE_RESULT); + startActivity(composeIntent); }); setupDrawer(); @@ -305,29 +306,25 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut R.drawable.ic_mute_24dp, getTheme()); ThemeUtils.setDrawableTint(this, muteDrawable, R.attr.toolbar_icon_tint); - List listItem = new ArrayList<>(); - listItem.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_EDIT_PROFILE).withName(getString(R.string.action_edit_profile)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_person)); - listItem.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_FAVOURITES).withName(getString(R.string.action_view_favourites)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_star)); - listItem.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_LISTS).withName(R.string.action_lists).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_list)); - listItem.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_MUTED_USERS).withName(getString(R.string.action_view_mutes)).withSelectable(false).withIcon(muteDrawable)); - listItem.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_BLOCKED_USERS).withName(getString(R.string.action_view_blocks)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_block)); - listItem.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_SEARCH).withName(getString(R.string.action_search)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_search)); - listItem.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_SAVED_TOOT).withName(getString(R.string.action_access_saved_toot)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_save)); - listItem.add(new DividerDrawerItem()); - listItem.add(new SecondaryDrawerItem().withIdentifier(DRAWER_ITEM_PREFERENCES).withName(getString(R.string.action_view_preferences)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_settings)); - listItem.add(new SecondaryDrawerItem().withIdentifier(DRAWER_ITEM_ABOUT).withName(getString(R.string.about_title_activity)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_info)); - listItem.add(new SecondaryDrawerItem().withIdentifier(DRAWER_ITEM_LOG_OUT).withName(getString(R.string.action_logout)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_exit_to_app)); - - IDrawerItem[] array = new IDrawerItem[listItem.size()]; - listItem.toArray(array); // fill the array + List listItems = new ArrayList<>(11); + listItems.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_EDIT_PROFILE).withName(R.string.action_edit_profile).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_person)); + listItems.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_FAVOURITES).withName(R.string.action_view_favourites).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_star)); + listItems.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_LISTS).withName(R.string.action_lists).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_list)); + listItems.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_MUTED_USERS).withName(R.string.action_view_mutes).withSelectable(false).withIcon(muteDrawable)); + listItems.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_BLOCKED_USERS).withName(R.string.action_view_blocks).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_block)); + listItems.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_SEARCH).withName(R.string.action_search).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_search)); + listItems.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_SAVED_TOOT).withName(R.string.action_access_saved_toot).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_save)); + listItems.add(new DividerDrawerItem()); + listItems.add(new SecondaryDrawerItem().withIdentifier(DRAWER_ITEM_PREFERENCES).withName(R.string.action_view_preferences).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_settings)); + listItems.add(new SecondaryDrawerItem().withIdentifier(DRAWER_ITEM_ABOUT).withName(R.string.about_title_activity).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_info)); + listItems.add(new SecondaryDrawerItem().withIdentifier(DRAWER_ITEM_LOG_OUT).withName(R.string.action_logout).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_exit_to_app)); drawer = new DrawerBuilder() .withActivity(this) - //.withToolbar(toolbar) .withAccountHeader(headerResult) .withHasStableIds(true) .withSelectedItem(-1) - .addDrawerItems(array) + .withDrawerItems(listItems) .withOnDrawerItemClickListener((view, position, drawerItem) -> { if (drawerItem != null) { long drawerItemIdentifier = drawerItem.getIdentifier(); @@ -490,7 +487,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut .withSelectable(false) .withIcon(GoogleMaterial.Icon.gmd_person_add); drawer.addItemAtPosition(followRequestsItem, 3); - } else { + } else if(!me.getLocked()){ drawer.removeItem(DRAWER_ITEM_FOLLOW_REQUESTS); } @@ -502,24 +499,29 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut List allAccounts = accountManager.getAllAccountsOrderedByActive(); - //remove profiles before adding them again to avoid duplicates - List profiles = new ArrayList<>(headerResult.getProfiles()); - for (IProfile profile : profiles) { - if (profile.getIdentifier() != DRAWER_ITEM_ADD_ACCOUNT) { - headerResult.removeProfile(profile); + // reuse the already existing "add account" item + List profiles = new ArrayList<>(allAccounts.size()+1); + for (IProfile profile: headerResult.getProfiles()) { + if (profile.getIdentifier() == DRAWER_ITEM_ADD_ACCOUNT) { + profiles.add(profile); + break; } } for (AccountEntity acc : allAccounts) { - headerResult.addProfiles( + CharSequence emojifiedName = CustomEmojiHelper.emojifyString(acc.getDisplayName(), acc.getEmojis(), headerResult.getView()); + emojifiedName = EmojiCompat.get().process(emojifiedName); + + profiles.add(0, new ProfileDrawerItem() - .withName(acc.getDisplayName()) + .withName(emojifiedName) .withIcon(acc.getProfilePictureUrl()) .withNameShown(true) .withIdentifier(acc.getId()) .withEmail(acc.getFullName())); } + headerResult.setProfiles(profiles); } diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java index bb840da7..1e723114 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java +++ b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java @@ -70,7 +70,8 @@ public class TuskyApplication extends Application implements HasActivityInjector appDatabase = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "tuskyDB") .allowMainThreadQueries() - .addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5, AppDatabase.MIGRATION_5_6, AppDatabase.MIGRATION_6_7) + .addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5, + AppDatabase.MIGRATION_5_6, AppDatabase.MIGRATION_6_7, AppDatabase.MIGRATION_7_8) .build(); accountManager = new AccountManager(appDatabase); serviceLocator = new ServiceLocator() { diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt index ccf87360..1585f43c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt @@ -18,9 +18,13 @@ package com.keylesspalace.tusky.db import android.arch.persistence.room.Entity import android.arch.persistence.room.Index import android.arch.persistence.room.PrimaryKey +import android.arch.persistence.room.TypeConverters + +import com.keylesspalace.tusky.entity.Emoji @Entity(indices = [Index(value = ["domain", "accountId"], unique = true)]) +@TypeConverters(Converters::class) data class AccountEntity(@field:PrimaryKey(autoGenerate = true) var id: Long, val domain: String, var accessToken: String, @@ -38,7 +42,8 @@ data class AccountEntity(@field:PrimaryKey(autoGenerate = true) var id: Long, var notificationVibration: Boolean = true, var notificationLight: Boolean = true, var lastNotificationId: String = "0", - var activeNotifications: String = "[]") { + var activeNotifications: String = "[]", + var emojis: List = emptyList()) { val identifier: String get() = "$domain:$accountId" diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt b/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt index e3c1c667..c9832e09 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt @@ -111,6 +111,7 @@ class AccountManager(db: AppDatabase) { it.username = account.username it.displayName = account.name it.profilePictureUrl = account.avatar + it.emojis = account.emojis ?: emptyList() Log.d(TAG, "updateActiveAccount: saving account with id " + it.id) it.id = accountDao.insertOrReplace(it) diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java index e2ec737c..b0106a44 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java +++ b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java @@ -25,7 +25,7 @@ import android.support.annotation.NonNull; * DB version & declare DAO */ -@Database(entities = {TootEntity.class, AccountEntity.class, InstanceEntity.class}, version = 7, exportSchema = false) +@Database(entities = {TootEntity.class, AccountEntity.class, InstanceEntity.class}, version = 8, exportSchema = false) public abstract class AppDatabase extends RoomDatabase { public abstract TootDao tootDao(); @@ -87,8 +87,15 @@ public abstract class AppDatabase extends RoomDatabase { @Override public void migrate(@NonNull SupportSQLiteDatabase database) { database.execSQL("CREATE TABLE IF NOT EXISTS `InstanceEntity` (`instance` TEXT NOT NULL, `emojiList` TEXT, `maximumTootCharacters` INTEGER, PRIMARY KEY(`instance`))"); - database.execSQL("INSERT OR REPLACE INTO `InstanceEntity` SELECT `instance`,`emojiList`,NULL FROM `EmojiListEntity`;"); + database.execSQL("INSERT OR REPLACE INTO `InstanceEntity` SELECT `instance`,`emojiList`, NULL FROM `EmojiListEntity`;"); database.execSQL("DROP TABLE `EmojiListEntity`;"); } }; + + public static final Migration MIGRATION_7_8 = new Migration(7, 8) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `emojis` TEXT NOT NULL DEFAULT '[]'"); + } + }; } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt b/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt new file mode 100644 index 00000000..76b7906b --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt @@ -0,0 +1,36 @@ +/* Copyright 2018 Conny Duck + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.db + +import android.arch.persistence.room.TypeConverter +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.keylesspalace.tusky.entity.Emoji + +class Converters { + + private val gson = Gson() + + @TypeConverter + fun jsonToEmojiList(emojiListJson: String?): List? { + return gson.fromJson(emojiListJson, object : TypeToken>() {}.type) + } + + @TypeConverter + fun emojiListToJson(emojiList: List?): String { + return gson.toJson(emojiList) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/db/InstanceEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/InstanceEntity.kt index 95ce1fc3..587b258c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/InstanceEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/InstanceEntity.kt @@ -17,10 +17,7 @@ package com.keylesspalace.tusky.db import android.arch.persistence.room.Entity import android.arch.persistence.room.PrimaryKey -import android.arch.persistence.room.TypeConverter import android.arch.persistence.room.TypeConverters -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken import com.keylesspalace.tusky.entity.Emoji @Entity @@ -29,17 +26,3 @@ data class InstanceEntity( @field:PrimaryKey var instance: String, val emojiList: List?, val maximumTootCharacters: Int?) - - -class Converters { - - @TypeConverter - fun jsonToList(emojiListJson: String?): List? { - return Gson().fromJson(emojiListJson, object : TypeToken>() {}.type) - } - - @TypeConverter - fun listToJson(emojiList: List?): String { - return Gson().toJson(emojiList) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.java b/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.java index 7b774153..04f3b04c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.java @@ -26,7 +26,7 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.SpannedString; import android.text.style.ReplacementSpan; -import android.widget.TextView; +import android.view.View; import com.keylesspalace.tusky.entity.Emoji; import com.squareup.picasso.Picasso; @@ -43,10 +43,10 @@ public class CustomEmojiHelper { * replaces emoji shortcodes in a text with EmojiSpans * @param text the text containing custom emojis * @param emojis a list of the custom emojis (nullable for backward compatibility with old mastodon instances) - * @param textView a reference to the textView the emojis will be shown in + * @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 */ - public static Spanned emojifyText(@NonNull Spanned text, @Nullable List emojis, @NonNull final TextView textView) { + public static Spanned emojifyText(@NonNull Spanned text, @Nullable List emojis, @NonNull final View view) { if (emojis != null && !emojis.isEmpty()) { @@ -57,9 +57,9 @@ public class CustomEmojiHelper { while (matcher.find()) { // We keep a span as a Picasso target, because Picasso keeps weak reference to // the target so an anonymous class would likely be garbage collected. - EmojiSpan span = new EmojiSpan(textView); + EmojiSpan span = new EmojiSpan(view); builder.setSpan(span, matcher.start(), matcher.end(), 0); - Picasso.with(textView.getContext()) + Picasso.with(view.getContext()) .load(emoji.getUrl()) .into(span); } @@ -71,18 +71,18 @@ public class CustomEmojiHelper { return text; } - public static Spanned emojifyString(@NonNull String string, @Nullable List emojis, @NonNull final TextView textView) { - return emojifyText(new SpannedString(string), emojis, textView); + public static Spanned emojifyString(@NonNull String string, @Nullable List emojis, @NonNull final View ciew) { + return emojifyText(new SpannedString(string), emojis, ciew); } public static class EmojiSpan extends ReplacementSpan implements Target { private @Nullable Drawable imageDrawable; - private WeakReference textViewWeakReference; + private WeakReference viewWeakReference; - EmojiSpan(TextView textView) { - this.textViewWeakReference = new WeakReference<>(textView); + EmojiSpan(View view) { + this.viewWeakReference = new WeakReference<>(view); } @Override @@ -120,10 +120,10 @@ public class CustomEmojiHelper { @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { - TextView textView = textViewWeakReference.get(); - if(textView != null) { - imageDrawable = new BitmapDrawable(textView.getContext().getResources(), bitmap); - textView.invalidate(); + View view = viewWeakReference.get(); + if(view != null) { + imageDrawable = new BitmapDrawable(view.getContext().getResources(), bitmap); + view.invalidate(); } }