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
This commit is contained in:
Konrad Pozniak 2018-07-30 15:43:27 +02:00 committed by GitHub
parent f44921f739
commit 759fd0f11b
9 changed files with 99 additions and 64 deletions

View File

@ -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"

View File

@ -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<Fragment> 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<IDrawerItem> 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<IDrawerItem> 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<AccountEntity> allAccounts = accountManager.getAllAccountsOrderedByActive();
//remove profiles before adding them again to avoid duplicates
List<IProfile> 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<IProfile> 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);
}

View File

@ -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() {

View File

@ -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<Emoji> = emptyList()) {
val identifier: String
get() = "$domain:$accountId"

View File

@ -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)

View File

@ -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 '[]'");
}
};
}

View File

@ -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 <http://www.gnu.org/licenses>. */
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<Emoji>? {
return gson.fromJson(emojiListJson, object : TypeToken<List<Emoji>>() {}.type)
}
@TypeConverter
fun emojiListToJson(emojiList: List<Emoji>?): String {
return gson.toJson(emojiList)
}
}

View File

@ -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<Emoji>?,
val maximumTootCharacters: Int?)
class Converters {
@TypeConverter
fun jsonToList(emojiListJson: String?): List<Emoji>? {
return Gson().fromJson(emojiListJson, object : TypeToken<List<Emoji>>() {}.type)
}
@TypeConverter
fun listToJson(emojiList: List<Emoji>?): String {
return Gson().toJson(emojiList)
}
}

View File

@ -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<Emoji> emojis, @NonNull final TextView textView) {
public static Spanned emojifyText(@NonNull Spanned text, @Nullable List<Emoji> 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<Emoji> emojis, @NonNull final TextView textView) {
return emojifyText(new SpannedString(string), emojis, textView);
public static Spanned emojifyString(@NonNull String string, @Nullable List<Emoji> 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<TextView> textViewWeakReference;
private WeakReference<View> 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();
}
}