convert MainActivity to Kotlin and upgrade MaterialDrawer to version 8 (#1748)

* convert MainActivity to Kotlin

* migrate to MaterialDrawer 8

* fix drawer styles

* revert removing BezelImageView and material_drawer_header override

* fix tests

* add lost comment back to material_drawer_header.xml

* add tools:parentTag to material_drawer_header.xml

* use when instead of if in MainActivity

* fix statusbar color over the drawer

* cleanup drawer item creation

* tint secondary drawer items as well

* remove unnecessary ids

* fix header text color in the light theme

* improve header text contrast
This commit is contained in:
Konrad Pozniak 2020-04-15 18:57:53 +02:00 committed by GitHub
parent d44eada140
commit 2cf1e366b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 878 additions and 791 deletions

View File

@ -101,6 +101,7 @@ ext.retrofitVersion = '2.8.1'
ext.okhttpVersion = '4.4.0'
ext.glideVersion = '4.11.0'
ext.daggerVersion = '2.27'
ext.materialdrawerVersion = '8.0.1'
// if libraries are changed here, they should also be changed in LicenseActivity
dependencies {
@ -159,10 +160,9 @@ dependencies {
implementation "com.github.chrisbanes:PhotoView:2.3.0"
implementation("com.mikepenz:materialdrawer:6.1.2@aar") {
transitive = true
}
implementation "com.mikepenz:google-material-typeface:3.0.1.3.original@aar"
implementation "com.mikepenz:materialdrawer:$materialdrawerVersion"
implementation "com.mikepenz:materialdrawer-iconics:$materialdrawerVersion"
implementation 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar'
implementation "com.theartofdev.edmodo:android-image-cropper:2.8.0"

View File

@ -138,7 +138,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
*/
private fun loadResources() {
toolbarColor = ThemeUtils.getColor(this, R.attr.colorSurface)
statusBarColorTransparent = ContextCompat.getColor(this, R.color.header_background_filter)
statusBarColorTransparent = ContextCompat.getColor(this, R.color.transparent_statusbar_background)
statusBarColorOpaque = ThemeUtils.getColor(this, R.attr.colorPrimaryDark)
avatarSize = resources.getDimension(R.dimen.account_activity_avatar_size)
titleVisibleHeight = resources.getDimensionPixelSize(R.dimen.account_activity_scroll_title_visible_height)

View File

@ -17,26 +17,26 @@ package com.keylesspalace.tusky
import android.Manifest
import android.app.Activity
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import com.google.android.material.snackbar.Snackbar
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import androidx.activity.viewModels
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.FitCenter
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.adapter.AccountFieldEditAdapter
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.di.ViewModelFactory
@ -44,8 +44,10 @@ import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Instance
import com.keylesspalace.tusky.util.*
import com.keylesspalace.tusky.viewmodel.EditProfileViewModel
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import com.theartofdev.edmodo.cropper.CropImage
import kotlinx.android.synthetic.main.activity_edit_profile.*
import kotlinx.android.synthetic.main.toolbar_basic.*
@ -103,7 +105,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
fieldList.layoutManager = LinearLayoutManager(this)
fieldList.adapter = accountFieldEditAdapter
val plusDrawable = IconicsDrawable(this, GoogleMaterial.Icon.gmd_add).sizeDp(12).color(Color.WHITE)
val plusDrawable = IconicsDrawable(this, GoogleMaterial.Icon.gmd_add).apply { sizeDp = 12; colorInt = Color.WHITE }
addFieldButton.setCompoundDrawablesRelativeWithIntrinsicBounds(plusDrawable, null, null, null)

View File

@ -39,8 +39,12 @@ import com.keylesspalace.tusky.util.*
import com.keylesspalace.tusky.viewmodel.ListsViewModel
import com.keylesspalace.tusky.viewmodel.ListsViewModel.Event.*
import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.*
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.color
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import com.mikepenz.iconics.utils.toIconicsColor
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
import com.uber.autodispose.autoDispose
import dagger.android.DispatchingAndroidInjector
@ -235,7 +239,7 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
.apply {
val context = nameTextView.context
val iconColor = ThemeUtils.getColor(context, android.R.attr.textColorTertiary)
val icon = IconicsDrawable(context, GoogleMaterial.Icon.gmd_list).sizeDp(20).color(iconColor)
val icon = IconicsDrawable(context, GoogleMaterial.Icon.gmd_list).apply { sizeDp = 20; colorInt = iconColor }
nameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null)
}

View File

@ -1,663 +0,0 @@
/* Copyright 2017 Andrew Dawson
*
* 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;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.emoji.text.EmojiCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.preference.PreferenceManager;
import androidx.viewpager2.widget.MarginPageTransformer;
import androidx.viewpager2.widget.ViewPager2;
import com.bumptech.glide.Glide;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import com.keylesspalace.tusky.appstore.CacheUpdater;
import com.keylesspalace.tusky.appstore.EventHub;
import com.keylesspalace.tusky.appstore.MainTabsChangedEvent;
import com.keylesspalace.tusky.appstore.ProfileEditedEvent;
import com.keylesspalace.tusky.components.compose.ComposeActivity;
import com.keylesspalace.tusky.components.conversation.ConversationsRepository;
import com.keylesspalace.tusky.components.scheduled.ScheduledTootActivity;
import com.keylesspalace.tusky.components.search.SearchActivity;
import com.keylesspalace.tusky.db.AccountEntity;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.fragment.SFragment;
import com.keylesspalace.tusky.interfaces.ActionButtonActivity;
import com.keylesspalace.tusky.interfaces.ReselectableFragment;
import com.keylesspalace.tusky.pager.MainPagerAdapter;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.NotificationHelper;
import com.keylesspalace.tusky.util.ShareShortcutHelper;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.materialdrawer.AccountHeader;
import com.mikepenz.materialdrawer.AccountHeaderBuilder;
import com.mikepenz.materialdrawer.Drawer;
import com.mikepenz.materialdrawer.DrawerBuilder;
import com.mikepenz.materialdrawer.model.DividerDrawerItem;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem;
import com.mikepenz.materialdrawer.model.SecondaryDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IProfile;
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader;
import com.mikepenz.materialdrawer.util.DrawerImageLoader;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import dagger.android.AndroidInjector;
import dagger.android.DispatchingAndroidInjector;
import dagger.android.HasAndroidInjector;
import io.reactivex.android.schedulers.AndroidSchedulers;
import static com.keylesspalace.tusky.util.MediaUtilsKt.deleteStaleCachedMedia;
import static com.uber.autodispose.AutoDispose.autoDisposable;
import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from;
public final class MainActivity extends BottomSheetActivity implements ActionButtonActivity,
HasAndroidInjector {
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;
private static final long DRAWER_ITEM_FAVOURITES = 1;
private static final long DRAWER_ITEM_BOOKMARKS = 2;
private static final long DRAWER_ITEM_LISTS = 3;
private static final long DRAWER_ITEM_SEARCH = 4;
private static final long DRAWER_ITEM_SAVED_TOOT = 5;
private static final long DRAWER_ITEM_ACCOUNT_SETTINGS = 6;
private static final long DRAWER_ITEM_SETTINGS = 7;
private static final long DRAWER_ITEM_ABOUT = 8;
private static final long DRAWER_ITEM_LOG_OUT = 9;
private static final long DRAWER_ITEM_FOLLOW_REQUESTS = 10;
private static final long DRAWER_ITEM_SCHEDULED_TOOT = 11;
public static final String STATUS_URL = "statusUrl";
@Inject
public DispatchingAndroidInjector<Object> androidInjector;
@Inject
public EventHub eventHub;
@Inject
public CacheUpdater cacheUpdater;
@Inject
ConversationsRepository conversationRepository;
private FloatingActionButton composeButton;
private AccountHeader headerResult;
private Drawer drawer;
private TabLayout tabLayout;
private ViewPager2 viewPager;
private int notificationTabPosition;
private MainPagerAdapter adapter;
private final EmojiCompat.InitCallback emojiInitCallback = new EmojiCompat.InitCallback() {
@Override
public void onInitialized() {
if(!isDestroyed()) {
updateProfiles();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (accountManager.getActiveAccount() == null) {
// will be redirected to LoginActivity by BaseActivity
return;
}
Intent intent = getIntent();
boolean showNotificationTab = false;
if (intent != null) {
/** there are two possibilities the accountId can be passed to MainActivity:
- from our code as long 'account_id'
- from share shortcuts as String 'android.intent.extra.shortcut.ID'
*/
long accountId = intent.getLongExtra(NotificationHelper.ACCOUNT_ID, -1);
if(accountId == -1) {
String accountIdString = intent.getStringExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID);
if(accountIdString != null) {
accountId = Long.parseLong(accountIdString);
}
}
boolean accountRequested = (accountId != -1);
if (accountRequested) {
AccountEntity account = accountManager.getActiveAccount();
if (account == null || accountId != account.getId()) {
accountManager.setActiveAccount(accountId);
}
}
if (ComposeActivity.canHandleMimeType(intent.getType())) {
// Sharing to Tusky from an external app
if (accountRequested) {
// The correct account is already active
forwardShare(intent);
} else {
// No account was provided, show the chooser
showAccountChooserDialog(getString(R.string.action_share_as), true, account -> {
long requestedId = account.getId();
AccountEntity activeAccount = accountManager.getActiveAccount();
if (activeAccount != null && requestedId == activeAccount.getId()) {
// The correct account is already active
forwardShare(intent);
} else {
// A different account was requested, restart the activity
intent.putExtra(NotificationHelper.ACCOUNT_ID, requestedId);
changeAccount(requestedId, intent);
}
});
}
} else if (accountRequested) {
// user clicked a notification, show notification tab and switch user if necessary
showNotificationTab = true;
}
}
setContentView(R.layout.activity_main);
composeButton = findViewById(R.id.floating_btn);
tabLayout = findViewById(R.id.tab_layout);
viewPager = findViewById(R.id.pager);
composeButton.setOnClickListener(v -> {
Intent composeIntent = new Intent(getApplicationContext(), ComposeActivity.class);
startActivity(composeIntent);
});
setupDrawer();
/* Fetch user info while we're doing other things. This has to be done after setting up the
* drawer, though, because its callback touches the header in the drawer. */
fetchUserInfo();
setupTabs(showNotificationTab);
int pageMargin = getResources().getDimensionPixelSize(R.dimen.tab_page_margin);
viewPager.setPageTransformer(new MarginPageTransformer(pageMargin));
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
if (tab.getPosition() == notificationTabPosition) {
NotificationHelper.clearNotificationsForActiveAccount(MainActivity.this, accountManager);
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
if (adapter != null) {
Fragment fragment = adapter.getFragment(tab.getPosition());
if (fragment instanceof ReselectableFragment) {
((ReselectableFragment) fragment).onReselect();
}
}
}
});
// Setup push notifications
if (NotificationHelper.areNotificationsEnabled(this, accountManager)) {
NotificationHelper.enablePullNotifications();
} else {
NotificationHelper.disablePullNotifications();
}
eventHub.getEvents()
.observeOn(AndroidSchedulers.mainThread())
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
.subscribe(event -> {
if (event instanceof ProfileEditedEvent) {
onFetchUserInfoSuccess(((ProfileEditedEvent) event).getNewProfileData());
}
if (event instanceof MainTabsChangedEvent) {
setupTabs(false);
}
});
// Flush old media that was cached for sharing
deleteStaleCachedMedia(getApplicationContext().getExternalFilesDir("Tusky"));
}
@Override
protected void onResume() {
super.onResume();
NotificationHelper.clearNotificationsForActiveAccount(this, accountManager);
}
@Override
public void onBackPressed() {
if (drawer != null && drawer.isDrawerOpen()) {
drawer.closeDrawer();
} else if (viewPager.getCurrentItem() != 0) {
viewPager.setCurrentItem(0);
} else {
super.onBackPressed();
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_MENU: {
if (drawer.isDrawerOpen()) {
drawer.closeDrawer();
} else {
drawer.openDrawer();
}
return true;
}
case KeyEvent.KEYCODE_SEARCH: {
startActivityWithSlideInAnimation(SearchActivity.getIntent(this));
return true;
}
}
if (event.isCtrlPressed() || event.isShiftPressed()) {
// FIXME: blackberry keyONE raises SHIFT key event even CTRL IS PRESSED
switch (keyCode) {
case KeyEvent.KEYCODE_N: {
// open compose activity by pressing SHIFT + N (or CTRL + N)
Intent composeIntent = new Intent(getApplicationContext(), ComposeActivity.class);
startActivity(composeIntent);
return true;
}
}
}
return super.onKeyDown(keyCode, event);
}
@Override
public void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
Intent intent = getIntent();
if (intent != null) {
String statusUrl = intent.getStringExtra(STATUS_URL);
if (statusUrl != null) {
viewUrl(statusUrl, PostLookupFallbackBehavior.DISPLAY_ERROR);
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
EmojiCompat.get().unregisterInitCallback(emojiInitCallback);
}
private void forwardShare(Intent intent) {
Intent composeIntent = new Intent(this, ComposeActivity.class);
composeIntent.setAction(intent.getAction());
composeIntent.setType(intent.getType());
composeIntent.putExtras(intent);
composeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(composeIntent);
finish();
}
private void setupDrawer() {
headerResult = new AccountHeaderBuilder()
.withActivity(this)
.withDividerBelowHeader(false)
.withHeaderBackgroundScaleType(ImageView.ScaleType.CENTER_CROP)
.withCurrentProfileHiddenInList(true)
.withOnAccountHeaderListener((view, profile, current) -> handleProfileClick(profile, current))
.addProfiles(
new ProfileSettingDrawerItem()
.withIdentifier(DRAWER_ITEM_ADD_ACCOUNT)
.withName(R.string.add_account_name)
.withDescription(R.string.add_account_description)
.withIcon(GoogleMaterial.Icon.gmd_add))
.build();
headerResult.getView()
.findViewById(R.id.material_drawer_account_header_current)
.setContentDescription(getString(R.string.action_view_profile));
ImageView background = headerResult.getHeaderBackgroundView();
background.setColorFilter(ContextCompat.getColor(this, R.color.header_background_filter));
background.setBackgroundColor(ContextCompat.getColor(this, R.color.tusky_grey_10));
final boolean animateAvatars = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("animateGifAvatars", false);
DrawerImageLoader.init(new AbstractDrawerImageLoader() {
@Override
public void set(ImageView imageView, Uri uri, Drawable placeholder, String tag) {
if(animateAvatars) {
Glide.with(MainActivity.this)
.load(uri)
.placeholder(placeholder)
.into(imageView);
} else {
Glide.with(MainActivity.this)
.asBitmap()
.load(uri)
.placeholder(placeholder)
.into(imageView);
}
}
@Override
public void cancel(ImageView imageView) {
Glide.with(MainActivity.this).clear(imageView);
}
});
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_BOOKMARKS).withName(R.string.action_view_bookmarks).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_bookmark));
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_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(R.drawable.ic_notebook).withIconTintingEnabled(true));
listItems.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_SCHEDULED_TOOT).withName(R.string.action_access_scheduled_toot).withSelectable(false).withIcon(R.drawable.ic_access_time).withIconTintingEnabled(true));
listItems.add(new DividerDrawerItem());
listItems.add(new SecondaryDrawerItem().withIdentifier(DRAWER_ITEM_ACCOUNT_SETTINGS).withName(R.string.action_view_account_preferences).withSelectable(false).withIcon(R.drawable.ic_account_settings).withIconTintingEnabled(true));
listItems.add(new SecondaryDrawerItem().withIdentifier(DRAWER_ITEM_SETTINGS).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(R.drawable.ic_logout).withIconTintingEnabled(true));
drawer = new DrawerBuilder()
.withActivity(this)
.withAccountHeader(headerResult)
.withHasStableIds(true)
.withSelectedItem(-1)
.withDrawerItems(listItems)
.withToolbar(findViewById(R.id.main_toolbar))
.withOnDrawerItemClickListener((view, position, drawerItem) -> {
if (drawerItem != null) {
long drawerItemIdentifier = drawerItem.getIdentifier();
if (drawerItemIdentifier == DRAWER_ITEM_EDIT_PROFILE) {
Intent intent = new Intent(MainActivity.this, EditProfileActivity.class);
startActivityWithSlideInAnimation(intent);
} else if (drawerItemIdentifier == DRAWER_ITEM_FAVOURITES) {
Intent intent = StatusListActivity.newFavouritesIntent(MainActivity.this);
startActivityWithSlideInAnimation(intent);
} else if (drawerItemIdentifier == DRAWER_ITEM_BOOKMARKS) {
Intent intent = StatusListActivity.newBookmarksIntent(MainActivity.this);
startActivityWithSlideInAnimation(intent);
} else if (drawerItemIdentifier == DRAWER_ITEM_SEARCH) {
startActivityWithSlideInAnimation(SearchActivity.getIntent(this));
} else if (drawerItemIdentifier == DRAWER_ITEM_ACCOUNT_SETTINGS) {
Intent intent = PreferencesActivity.newIntent(MainActivity.this, PreferencesActivity.ACCOUNT_PREFERENCES);
startActivityWithSlideInAnimation(intent);
} else if (drawerItemIdentifier == DRAWER_ITEM_SETTINGS) {
Intent intent = PreferencesActivity.newIntent(MainActivity.this, PreferencesActivity.GENERAL_PREFERENCES);
startActivityWithSlideInAnimation(intent);
} else if (drawerItemIdentifier == DRAWER_ITEM_ABOUT) {
Intent intent = new Intent(MainActivity.this, AboutActivity.class);
startActivityWithSlideInAnimation(intent);
} else if (drawerItemIdentifier == DRAWER_ITEM_LOG_OUT) {
logout();
} else if (drawerItemIdentifier == DRAWER_ITEM_FOLLOW_REQUESTS) {
Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
intent.putExtra("type", AccountListActivity.Type.FOLLOW_REQUESTS);
startActivityWithSlideInAnimation(intent);
} else if (drawerItemIdentifier == DRAWER_ITEM_SAVED_TOOT) {
Intent intent = new Intent(MainActivity.this, SavedTootActivity.class);
startActivityWithSlideInAnimation(intent);
} else if (drawerItemIdentifier == DRAWER_ITEM_SCHEDULED_TOOT) {
startActivityWithSlideInAnimation(ScheduledTootActivity.newIntent(this));
} else if (drawerItemIdentifier == DRAWER_ITEM_LISTS) {
startActivityWithSlideInAnimation(ListsActivity.newIntent(this));
}
}
return false;
})
.build();
if (BuildConfig.DEBUG) {
IDrawerItem debugItem = new SecondaryDrawerItem()
.withIdentifier(1337)
.withName("debug")
.withDisabledTextColor(Color.GREEN)
.withSelectable(false)
.withEnabled(false);
drawer.addItem(debugItem);
}
EmojiCompat.get().registerInitCallback(emojiInitCallback);
}
private void setupTabs(boolean selectNotificationTab) {
List<TabData> tabs = accountManager.getActiveAccount().getTabPreferences();
adapter = new MainPagerAdapter(tabs, this);
viewPager.setAdapter(adapter);
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> { }).attach();
tabLayout.removeAllTabs();
for (int i = 0; i < tabs.size(); i++) {
TabLayout.Tab tab = tabLayout.newTab()
.setIcon(tabs.get(i).getIcon());
if (tabs.get(i).getId().equals(TabDataKt.LIST)) {
tab.setContentDescription(tabs.get(i).getArguments().get(1));
} else {
tab.setContentDescription(tabs.get(i).getText());
}
tabLayout.addTab(tab);
if (tabs.get(i).getId().equals(TabDataKt.NOTIFICATIONS)) {
notificationTabPosition = i;
if (selectNotificationTab) {
tab.select();
}
}
}
}
private boolean handleProfileClick(IProfile profile, boolean current) {
AccountEntity activeAccount = accountManager.getActiveAccount();
//open profile when active image was clicked
if (current && activeAccount != null) {
Intent intent = AccountActivity.getIntent(this, activeAccount.getAccountId());
startActivityWithSlideInAnimation(intent);
new Handler().postDelayed(() -> drawer.closeDrawer(), 100);
return true;
}
//open LoginActivity to add new account
if (profile.getIdentifier() == DRAWER_ITEM_ADD_ACCOUNT) {
startActivityWithSlideInAnimation(LoginActivity.getIntent(this, true));
new Handler().postDelayed(() -> drawer.closeDrawer(), 100);
return true;
}
//change Account
changeAccount(profile.getIdentifier(), null);
return false;
}
private void changeAccount(long newSelectedId, @Nullable Intent forward) {
cacheUpdater.stop();
SFragment.flushFilters();
accountManager.setActiveAccount(newSelectedId);
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
if (forward != null) {
intent.setType(forward.getType());
intent.setAction(forward.getAction());
intent.putExtras(forward);
}
startActivity(intent);
finishWithoutSlideOutAnimation();
overridePendingTransition(R.anim.explode, R.anim.explode);
}
private void logout() {
AccountEntity activeAccount = accountManager.getActiveAccount();
if (activeAccount != null) {
new AlertDialog.Builder(this)
.setTitle(R.string.action_logout)
.setMessage(getString(R.string.action_logout_confirm, activeAccount.getFullName()))
.setPositiveButton(android.R.string.yes, (dialog, which) -> {
NotificationHelper.deleteNotificationChannelsForAccount(accountManager.getActiveAccount(), MainActivity.this);
cacheUpdater.clearForUser(activeAccount.getId());
conversationRepository.deleteCacheForAccount(activeAccount.getId());
ShareShortcutHelper.removeShortcut(this, activeAccount);
AccountEntity newAccount = accountManager.logActiveAccountOut();
if (!NotificationHelper.areNotificationsEnabled(MainActivity.this, accountManager)) {
NotificationHelper.disablePullNotifications();
}
Intent intent;
if (newAccount == null) {
intent = LoginActivity.getIntent(MainActivity.this, false);
} else {
intent = new Intent(MainActivity.this, MainActivity.class);
}
startActivity(intent);
finishWithoutSlideOutAnimation();
})
.setNegativeButton(android.R.string.no, null)
.show();
}
}
private void fetchUserInfo() {
mastodonApi.accountVerifyCredentials()
.observeOn(AndroidSchedulers.mainThread())
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
.subscribe(this::onFetchUserInfoSuccess, MainActivity::onFetchUserInfoFailure);
}
private void onFetchUserInfoSuccess(Account me) {
// Add the header image and avatar from the account, into the navigation drawer header.
ImageView background = headerResult.getHeaderBackgroundView();
Glide.with(MainActivity.this)
.asBitmap()
.load(me.getHeader())
.into(background);
accountManager.updateActiveAccount(me);
NotificationHelper.createNotificationChannelsForAccount(accountManager.getActiveAccount(), this);
// Show follow requests in the menu, if this is a locked account.
if (me.getLocked() && drawer.getDrawerItem(DRAWER_ITEM_FOLLOW_REQUESTS) == null) {
PrimaryDrawerItem followRequestsItem = new PrimaryDrawerItem()
.withIdentifier(DRAWER_ITEM_FOLLOW_REQUESTS)
.withName(R.string.action_view_follow_requests)
.withSelectable(false)
.withIcon(GoogleMaterial.Icon.gmd_person_add);
drawer.addItemAtPosition(followRequestsItem, 4);
} else if (!me.getLocked()) {
drawer.removeItem(DRAWER_ITEM_FOLLOW_REQUESTS);
}
updateProfiles();
ShareShortcutHelper.updateShortcut(this, accountManager.getActiveAccount());
}
private void updateProfiles() {
List<AccountEntity> allAccounts = accountManager.getAllAccountsOrderedByActive();
List<IProfile> profiles = new ArrayList<>(allAccounts.size() + 1);
for (AccountEntity acc : allAccounts) {
CharSequence emojifiedName = CustomEmojiHelper.emojifyString(acc.getDisplayName(), acc.getEmojis(), headerResult.getView());
emojifiedName = EmojiCompat.get().process(emojifiedName);
profiles.add(
new ProfileDrawerItem()
.withSetSelected(acc.isActive())
.withName(emojifiedName)
.withIcon(acc.getProfilePictureUrl())
.withNameShown(true)
.withIdentifier(acc.getId())
.withEmail(acc.getFullName()));
}
// reuse the already existing "add account" item
for (IProfile profile : headerResult.getProfiles()) {
if (profile.getIdentifier() == DRAWER_ITEM_ADD_ACCOUNT) {
profiles.add(profile);
break;
}
}
headerResult.clear();
headerResult.setProfiles(profiles);
headerResult.setActiveProfile(accountManager.getActiveAccount().getId());
}
private static void onFetchUserInfoFailure(Throwable throwable) {
Log.e(TAG, "Failed to fetch user info. " + throwable.getMessage());
}
@Nullable
@Override
public FloatingActionButton getActionButton() {
return composeButton;
}
@Override
public AndroidInjector<Object> androidInjector() {
return androidInjector;
}
}

View File

@ -0,0 +1,650 @@
/* Copyright 2020 Tusky Contributors
*
* 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
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.emoji.text.EmojiCompat
import androidx.emoji.text.EmojiCompat.InitCallback
import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager
import androidx.viewpager2.widget.MarginPageTransformer
import com.bumptech.glide.Glide
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import com.google.android.material.tabs.TabLayoutMediator
import com.google.android.material.tabs.TabLayoutMediator.TabConfigurationStrategy
import com.keylesspalace.tusky.appstore.*
import com.keylesspalace.tusky.components.compose.ComposeActivity
import com.keylesspalace.tusky.components.compose.ComposeActivity.Companion.canHandleMimeType
import com.keylesspalace.tusky.components.conversation.ConversationsRepository
import com.keylesspalace.tusky.components.scheduled.ScheduledTootActivity
import com.keylesspalace.tusky.components.search.SearchActivity
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.fragment.SFragment
import com.keylesspalace.tusky.interfaces.AccountSelectionListener
import com.keylesspalace.tusky.interfaces.ActionButtonActivity
import com.keylesspalace.tusky.interfaces.ReselectableFragment
import com.keylesspalace.tusky.pager.MainPagerAdapter
import com.keylesspalace.tusky.util.*
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.materialdrawer.iconics.iconicsIcon
import com.mikepenz.materialdrawer.model.*
import com.mikepenz.materialdrawer.model.interfaces.*
import com.mikepenz.materialdrawer.util.*
import com.mikepenz.materialdrawer.widget.AccountHeaderView
import com.uber.autodispose.android.lifecycle.autoDispose
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.activity_main.*
import javax.inject.Inject
class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInjector {
@Inject
lateinit var androidInjector: DispatchingAndroidInjector<Any>
@Inject
lateinit var eventHub: EventHub
@Inject
lateinit var cacheUpdater: CacheUpdater
@Inject
lateinit var conversationRepository: ConversationsRepository
private lateinit var header: AccountHeaderView
private lateinit var drawerToggle: ActionBarDrawerToggle
private var notificationTabPosition = 0
private var adapter: MainPagerAdapter? = null
private val emojiInitCallback = object : InitCallback() {
override fun onInitialized() {
if (!isDestroyed) {
updateProfiles()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (accountManager.activeAccount == null) {
// will be redirected to LoginActivity by BaseActivity
return
}
var showNotificationTab = false
if (intent != null) {
/** there are two possibilities the accountId can be passed to MainActivity:
* - from our code as long 'account_id'
* - from share shortcuts as String 'android.intent.extra.shortcut.ID'
*/
var accountId = intent.getLongExtra(NotificationHelper.ACCOUNT_ID, -1)
if (accountId == -1L) {
val accountIdString = intent.getStringExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID)
if (accountIdString != null) {
accountId = accountIdString.toLong()
}
}
val accountRequested = accountId != -1L
if (accountRequested) {
val account = accountManager.activeAccount
if (account == null || accountId != account.id) {
accountManager.setActiveAccount(accountId)
}
}
if (canHandleMimeType(intent.type)) {
// Sharing to Tusky from an external app
if (accountRequested) {
// The correct account is already active
forwardShare(intent)
} else {
// No account was provided, show the chooser
showAccountChooserDialog(getString(R.string.action_share_as), true, object : AccountSelectionListener {
override fun onAccountSelected(account: AccountEntity) {
val requestedId = account.id
val activeAccount = accountManager.activeAccount
if (activeAccount != null && requestedId == activeAccount.id) {
// The correct account is already active
forwardShare(intent)
} else {
// A different account was requested, restart the activity
intent.putExtra(NotificationHelper.ACCOUNT_ID, requestedId)
changeAccount(requestedId, intent)
}
}
})
}
} else if (accountRequested) {
// user clicked a notification, show notification tab and switch user if necessary
showNotificationTab = true
}
}
window.statusBarColor = Color.TRANSPARENT // don't draw a status bar, the DrawerLayout and the MaterialDrawerLayout have their own
setContentView(R.layout.activity_main)
composeButton.setOnClickListener {
val composeIntent = Intent(applicationContext, ComposeActivity::class.java)
startActivity(composeIntent)
}
setupDrawer(savedInstanceState)
/* Fetch user info while we're doing other things. This has to be done after setting up the
* drawer, though, because its callback touches the header in the drawer. */
fetchUserInfo()
setupTabs(showNotificationTab)
val pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin)
viewPager.setPageTransformer(MarginPageTransformer(pageMargin))
tabLayout.addOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
if (tab.position == notificationTabPosition) {
NotificationHelper.clearNotificationsForActiveAccount(this@MainActivity, accountManager)
}
}
override fun onTabUnselected(tab: TabLayout.Tab) {}
override fun onTabReselected(tab: TabLayout.Tab) {
val fragment = adapter?.getFragment(tab.position)
if (fragment is ReselectableFragment) {
(fragment as ReselectableFragment).onReselect()
}
}
})
// Setup push notifications
if (NotificationHelper.areNotificationsEnabled(this, accountManager)) {
NotificationHelper.enablePullNotifications()
} else {
NotificationHelper.disablePullNotifications()
}
eventHub.events
.observeOn(AndroidSchedulers.mainThread())
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
.subscribe { event: Event? ->
when (event) {
is ProfileEditedEvent -> onFetchUserInfoSuccess(event.newProfileData)
is MainTabsChangedEvent -> setupTabs(false)
}
}
// Flush old media that was cached for sharing
deleteStaleCachedMedia(applicationContext.getExternalFilesDir("Tusky"))
}
override fun onResume() {
super.onResume()
NotificationHelper.clearNotificationsForActiveAccount(this, accountManager)
}
override fun onBackPressed() {
when {
mainDrawerLayout.isOpen -> {
mainDrawerLayout.close()
}
viewPager.currentItem != 0 -> {
viewPager.currentItem = 0
}
else -> {
super.onBackPressed()
}
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_MENU -> {
if (mainDrawerLayout.isOpen) {
mainDrawerLayout.close()
} else {
mainDrawerLayout.open()
}
return true
}
KeyEvent.KEYCODE_SEARCH -> {
startActivityWithSlideInAnimation(SearchActivity.getIntent(this))
return true
}
}
if (event.isCtrlPressed || event.isShiftPressed) {
// FIXME: blackberry keyONE raises SHIFT key event even CTRL IS PRESSED
when (keyCode) {
KeyEvent.KEYCODE_N -> {
// open compose activity by pressing SHIFT + N (or CTRL + N)
val composeIntent = Intent(applicationContext, ComposeActivity::class.java)
startActivity(composeIntent)
return true
}
}
}
return super.onKeyDown(keyCode, event)
}
public override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
drawerToggle.syncState()
if (intent != null) {
val statusUrl = intent.getStringExtra(STATUS_URL)
if (statusUrl != null) {
viewUrl(statusUrl, PostLookupFallbackBehavior.DISPLAY_ERROR)
}
}
}
override fun onDestroy() {
super.onDestroy()
EmojiCompat.get().unregisterInitCallback(emojiInitCallback)
}
private fun forwardShare(intent: Intent) {
val composeIntent = Intent(this, ComposeActivity::class.java)
composeIntent.action = intent.action
composeIntent.type = intent.type
composeIntent.putExtras(intent)
composeIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(composeIntent)
finish()
}
private fun setupDrawer(savedInstanceState: Bundle?) {
drawerToggle = ActionBarDrawerToggle(this, mainDrawerLayout, mainToolbar, com.mikepenz.materialdrawer.R.string.material_drawer_open, com.mikepenz.materialdrawer.R.string.material_drawer_close)
header = AccountHeaderView(this).apply {
headerBackgroundScaleType = ImageView.ScaleType.CENTER_CROP
currentHiddenInList = true
onAccountHeaderListener = { _: View?, profile: IProfile, current: Boolean -> handleProfileClick(profile, current) }
addProfile(ProfileSettingDrawerItem().apply {
identifier = DRAWER_ITEM_ADD_ACCOUNT
nameRes = R.string.add_account_name
descriptionRes = R.string.add_account_description
iconicsIcon = GoogleMaterial.Icon.gmd_add
}, 0)
attachToSliderView(mainDrawer)
dividerBelowHeader = false
closeDrawerOnProfileListClick = true
}
header.accountHeaderBackground.setColorFilter(ContextCompat.getColor(this, R.color.headerBackgroundFilter))
header.accountHeaderBackground.setBackgroundColor(ThemeUtils.getColor(this, R.attr.colorBackgroundAccent))
val animateAvatars = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("animateGifAvatars", false)
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) {
if (animateAvatars) {
Glide.with(imageView.context)
.load(uri)
.placeholder(placeholder)
.into(imageView)
} else {
Glide.with(imageView.context)
.asBitmap()
.load(uri)
.placeholder(placeholder)
.into(imageView)
}
}
override fun cancel(imageView: ImageView) {
Glide.with(imageView.context).clear(imageView)
}
override fun placeholder(ctx: Context, tag: String?): Drawable {
if (tag == DrawerImageLoader.Tags.PROFILE.name || tag == DrawerImageLoader.Tags.PROFILE_DRAWER_ITEM.name) {
return ctx.getDrawable(R.drawable.avatar_default)!!
}
return super.placeholder(ctx, tag)
}
})
mainDrawer.apply {
tintStatusBar = true
addItems(
primaryDrawerItem {
nameRes = R.string.action_edit_profile
iconicsIcon = GoogleMaterial.Icon.gmd_person
onClick = {
val intent = Intent(context, EditProfileActivity::class.java)
startActivityWithSlideInAnimation(intent)
}
},
primaryDrawerItem {
nameRes = R.string.action_view_favourites
isSelectable = false
iconicsIcon = GoogleMaterial.Icon.gmd_star
onClick = {
val intent = StatusListActivity.newFavouritesIntent(context)
startActivityWithSlideInAnimation(intent)
}
},
primaryDrawerItem {
nameRes = R.string.action_view_bookmarks
iconicsIcon = GoogleMaterial.Icon.gmd_bookmark
onClick = {
val intent = StatusListActivity.newBookmarksIntent(context)
startActivityWithSlideInAnimation(intent)
}
},
primaryDrawerItem {
nameRes = R.string.action_lists
iconicsIcon = GoogleMaterial.Icon.gmd_list
onClick = {
startActivityWithSlideInAnimation(ListsActivity.newIntent(context))
}
},
primaryDrawerItem {
nameRes = R.string.action_search
iconicsIcon = GoogleMaterial.Icon.gmd_search
onClick = {
startActivityWithSlideInAnimation(SearchActivity.getIntent(context))
}
},
primaryDrawerItem {
nameRes = R.string.action_access_saved_toot
iconRes = R.drawable.ic_notebook
onClick = {
val intent = Intent(context, SavedTootActivity::class.java)
startActivityWithSlideInAnimation(intent)
}
},
primaryDrawerItem {
nameRes = R.string.action_access_scheduled_toot
iconRes = R.drawable.ic_access_time
onClick = {
startActivityWithSlideInAnimation(ScheduledTootActivity.newIntent(context))
}
},
DividerDrawerItem(),
secondaryDrawerItem {
nameRes = R.string.action_view_account_preferences
iconRes = R.drawable.ic_account_settings
onClick = {
val intent = PreferencesActivity.newIntent(context, PreferencesActivity.ACCOUNT_PREFERENCES)
startActivityWithSlideInAnimation(intent)
}
},
secondaryDrawerItem {
nameRes = R.string.action_view_preferences
iconicsIcon = GoogleMaterial.Icon.gmd_settings
onClick = {
val intent = PreferencesActivity.newIntent(context, PreferencesActivity.GENERAL_PREFERENCES)
startActivityWithSlideInAnimation(intent)
}
},
secondaryDrawerItem {
nameRes = R.string.about_title_activity
iconicsIcon = GoogleMaterial.Icon.gmd_info
onClick = {
val intent = Intent(context, AboutActivity::class.java)
startActivityWithSlideInAnimation(intent)
}
},
secondaryDrawerItem {
nameRes = R.string.action_logout
iconRes = R.drawable.ic_logout
onClick = ::logout
}
)
setSavedInstance(savedInstanceState)
}
if (BuildConfig.DEBUG) {
mainDrawer.addItems(
secondaryDrawerItem {
nameText = "debug"
isEnabled = false
textColor = ColorStateList.valueOf(Color.GREEN)
}
)
}
EmojiCompat.get().registerInitCallback(emojiInitCallback)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
drawerToggle.onConfigurationChanged(newConfig)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (drawerToggle.onOptionsItemSelected(item)) {
return true
}
return super.onOptionsItemSelected(item)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(mainDrawer.saveInstanceState(outState))
}
private fun setupTabs(selectNotificationTab: Boolean) {
val tabs = accountManager.activeAccount!!.tabPreferences
adapter = MainPagerAdapter(tabs, this)
viewPager.adapter = adapter
TabLayoutMediator(tabLayout, viewPager, TabConfigurationStrategy { _: TabLayout.Tab?, _: Int -> }).attach()
tabLayout.removeAllTabs()
for (i in tabs.indices) {
val tab = tabLayout.newTab()
.setIcon(tabs[i].icon)
if (tabs[i].id == LIST) {
tab.contentDescription = tabs[i].arguments[1]
} else {
tab.setContentDescription(tabs[i].text)
}
tabLayout.addTab(tab)
if (tabs[i].id == NOTIFICATIONS) {
notificationTabPosition = i
if (selectNotificationTab) {
tab.select()
}
}
}
}
private fun handleProfileClick(profile: IProfile, current: Boolean): Boolean {
val activeAccount = accountManager.activeAccount
//open profile when active image was clicked
if (current && activeAccount != null) {
val intent = AccountActivity.getIntent(this, activeAccount.accountId)
startActivityWithSlideInAnimation(intent)
return false
}
//open LoginActivity to add new account
if (profile.identifier == DRAWER_ITEM_ADD_ACCOUNT) {
startActivityWithSlideInAnimation(LoginActivity.getIntent(this, true))
return false
}
//change Account
changeAccount(profile.identifier, null)
return false
}
private fun changeAccount(newSelectedId: Long, forward: Intent?) {
cacheUpdater.stop()
SFragment.flushFilters()
accountManager.setActiveAccount(newSelectedId)
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
if (forward != null) {
intent.type = forward.type
intent.action = forward.action
intent.putExtras(forward)
}
startActivity(intent)
finishWithoutSlideOutAnimation()
overridePendingTransition(R.anim.explode, R.anim.explode)
}
private fun logout() {
accountManager.activeAccount?.let { activeAccount ->
AlertDialog.Builder(this)
.setTitle(R.string.action_logout)
.setMessage(getString(R.string.action_logout_confirm, activeAccount.fullName))
.setPositiveButton(android.R.string.yes) { _: DialogInterface?, _: Int ->
NotificationHelper.deleteNotificationChannelsForAccount(activeAccount, this@MainActivity)
cacheUpdater.clearForUser(activeAccount.id)
conversationRepository.deleteCacheForAccount(activeAccount.id)
removeShortcut(this, activeAccount)
val newAccount = accountManager.logActiveAccountOut()
if (!NotificationHelper.areNotificationsEnabled(this@MainActivity, accountManager)) {
NotificationHelper.disablePullNotifications()
}
val intent: Intent
intent = if (newAccount == null) {
LoginActivity.getIntent(this@MainActivity, false)
} else {
Intent(this@MainActivity, MainActivity::class.java)
}
startActivity(intent)
finishWithoutSlideOutAnimation()
}
.setNegativeButton(android.R.string.no, null)
.show()
}
}
private fun fetchUserInfo() {
mastodonApi.accountVerifyCredentials()
.observeOn(AndroidSchedulers.mainThread())
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
.subscribe(
{ userInfo ->
onFetchUserInfoSuccess(userInfo)
},
{ throwable ->
Log.e(TAG, "Failed to fetch user info. " + throwable.message)
}
)
}
private fun onFetchUserInfoSuccess(me: Account) {
Glide.with(this)
.asBitmap()
.load(me.header)
.into(header.accountHeaderBackground)
accountManager.updateActiveAccount(me)
NotificationHelper.createNotificationChannelsForAccount(accountManager.activeAccount!!, this)
// Show follow requests in the menu, if this is a locked account.
if (me.locked && mainDrawer.getDrawerItem(DRAWER_ITEM_FOLLOW_REQUESTS) == null) {
val followRequestsItem = primaryDrawerItem {
identifier = DRAWER_ITEM_FOLLOW_REQUESTS
nameRes = R.string.action_view_follow_requests
iconicsIcon = GoogleMaterial.Icon.gmd_person_add
onClick = {
val intent = Intent(this@MainActivity, AccountListActivity::class.java)
intent.putExtra("type", AccountListActivity.Type.FOLLOW_REQUESTS)
startActivityWithSlideInAnimation(intent)
}
}
mainDrawer.addItemAtPosition(4, followRequestsItem)
} else if (!me.locked) {
mainDrawer.removeItems(DRAWER_ITEM_FOLLOW_REQUESTS)
}
updateProfiles()
updateShortcut(this, accountManager.activeAccount!!)
}
private fun updateProfiles() {
val profiles: MutableList<IProfile> = accountManager.getAllAccountsOrderedByActive().map { acc ->
val emojifiedName = EmojiCompat.get().process(CustomEmojiHelper.emojifyString(acc.displayName, acc.emojis, header))
ProfileDrawerItem().apply {
isSelected = acc.isActive
nameText = emojifiedName
iconUrl = acc.profilePictureUrl
isNameShown = true
identifier = acc.id
descriptionText = acc.fullName
}
}.toMutableList()
// reuse the already existing "add account" item
for (profile in header.profiles.orEmpty()) {
if (profile.identifier == DRAWER_ITEM_ADD_ACCOUNT) {
profiles.add(profile)
break
}
}
header.clear()
header.profiles = profiles
header.setActiveProfile(accountManager.activeAccount!!.id)
}
override fun getActionButton(): FloatingActionButton? = composeButton
override fun androidInjector() = androidInjector
companion object {
private const val TAG = "MainActivity" // logging tag
private const val DRAWER_ITEM_ADD_ACCOUNT: Long = -13
private const val DRAWER_ITEM_FOLLOW_REQUESTS: Long = 10
const val STATUS_URL = "statusUrl"
}
}
private inline fun primaryDrawerItem(block: PrimaryDrawerItem.() -> Unit): PrimaryDrawerItem {
return PrimaryDrawerItem()
.apply {
isSelectable = false
isIconTinted = true
}
.apply(block)
}
private inline fun secondaryDrawerItem(block: SecondaryDrawerItem.() -> Unit): SecondaryDrawerItem {
return SecondaryDrawerItem()
.apply {
isSelectable = false
isIconTinted = true
}
.apply(block)
}
private var AbstractDrawerItem<*, *>.onClick: () -> Unit
get() = throw UnsupportedOperationException()
set(value) {
onDrawerItemClickListener = { _, _, _ ->
value()
true
}
}

View File

@ -54,13 +54,14 @@ import com.keylesspalace.tusky.util.StatusDisplayOptions;
import com.keylesspalace.tusky.util.TimestampUtils;
import com.keylesspalace.tusky.viewdata.NotificationViewData;
import com.keylesspalace.tusky.viewdata.StatusViewData;
import com.mikepenz.iconics.utils.Utils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import at.connyduck.sparkbutton.helpers.Utils;
public class NotificationsAdapter extends RecyclerView.Adapter {
public interface AdapterDataSource<T> {
@ -140,7 +141,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
view.setLayoutParams(
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
Utils.convertDpToPx(parent.getContext(), 24)
Utils.dpToPx(parent.getContext(), 24)
)
);
return new RecyclerView.ViewHolder(view) {

View File

@ -49,7 +49,6 @@ import com.keylesspalace.tusky.viewdata.PollOptionViewData;
import com.keylesspalace.tusky.viewdata.PollViewData;
import com.keylesspalace.tusky.viewdata.PollViewDataKt;
import com.keylesspalace.tusky.viewdata.StatusViewData;
import com.mikepenz.iconics.utils.Utils;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
@ -59,6 +58,7 @@ import java.util.Locale;
import java.util.Objects;
import at.connyduck.sparkbutton.SparkButton;
import at.connyduck.sparkbutton.helpers.Utils;
import kotlin.collections.CollectionsKt;
import static com.keylesspalace.tusky.viewdata.PollViewDataKt.buildDescription;
@ -294,7 +294,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
avatarRadius = avatarRadius48dp;
} else {
int padding = Utils.convertDpToPx(avatar.getContext(), 12);
int padding = Utils.dpToPx(avatar.getContext(), 12);
avatar.setPaddingRelative(0, 0, padding, padding);
avatarInset.setVisibility(View.VISIBLE);

View File

@ -65,7 +65,6 @@ import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener
import com.keylesspalace.tusky.components.compose.view.ComposeScheduleView
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.di.ViewModelFactory
@ -74,8 +73,10 @@ import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.NewPoll
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.util.*
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.activity_compose.*
import java.io.File
@ -231,7 +232,7 @@ class ComposeActivity : BaseActivity(),
if (replyingStatusAuthor != null) {
composeReplyView.show()
composeReplyView.text = getString(R.string.replying_to, replyingStatusAuthor)
val arrowDownIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_arrow_drop_down).sizeDp(12)
val arrowDownIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_arrow_drop_down).apply { sizeDp = 12 }
ThemeUtils.setDrawableTint(this, arrowDownIcon, android.R.attr.textColorTertiary)
composeReplyView.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, arrowDownIcon, null)
@ -244,7 +245,7 @@ class ComposeActivity : BaseActivity(),
composeReplyView.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, arrowDownIcon, null)
} else {
composeReplyContentView.show()
val arrowUpIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_arrow_drop_up).sizeDp(12)
val arrowUpIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_arrow_drop_up).apply { sizeDp = 12 }
ThemeUtils.setDrawableTint(this, arrowUpIcon, android.R.attr.textColorTertiary)
composeReplyView.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, arrowUpIcon, null)
@ -364,13 +365,13 @@ class ComposeActivity : BaseActivity(),
val textColor = ThemeUtils.getColor(this, android.R.attr.textColorTertiary)
val cameraIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_camera_alt).color(textColor).sizeDp(18)
val cameraIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_camera_alt).apply { colorInt = textColor; sizeDp = 18 }
actionPhotoTake.setCompoundDrawablesRelativeWithIntrinsicBounds(cameraIcon, null, null, null)
val imageIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_image).color(textColor).sizeDp(18)
val imageIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_image).apply { colorInt = textColor; sizeDp = 18 }
actionPhotoPick.setCompoundDrawablesRelativeWithIntrinsicBounds(imageIcon, null, null, null)
val pollIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_poll).color(textColor).sizeDp(18)
val pollIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_poll).apply { colorInt = textColor; sizeDp = 18 }
addPollTextActionTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(pollIcon, null, null, null)
actionPhotoTake.setOnClickListener { initiateCameraApp() }

View File

@ -21,8 +21,10 @@ import android.util.AttributeSet
import com.google.android.material.button.MaterialButton
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.entity.Status
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
class TootButton
@JvmOverloads constructor(
@ -59,7 +61,7 @@ class TootButton
Status.Visibility.PRIVATE,
Status.Visibility.DIRECT -> {
setText(R.string.action_send)
IconicsDrawable(context, GoogleMaterial.Icon.gmd_lock).sizeDp(18).color(Color.WHITE)
IconicsDrawable(context, GoogleMaterial.Icon.gmd_lock).apply { sizeDp = 18; colorInt = Color.WHITE }
}
else -> {
null

View File

@ -121,7 +121,6 @@ class SearchActivity : BottomSheetActivity(), HasAndroidInjector {
override fun androidInjector() = androidInjector
companion object {
@JvmStatic
fun getIntent(context: Context) = Intent(context, SearchActivity::class.java)
}
}

View File

@ -35,8 +35,10 @@ import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.ThemeUtils
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeRes
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
@ -72,8 +74,6 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(),
private lateinit var publicFiltersPreference: Preference
private lateinit var threadFiltersPreference: Preference
private val iconSize by lazy { resources.getDimensionPixelSize(R.dimen.preference_icon_size) }
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.account_preferences)
@ -92,9 +92,9 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(),
publicFiltersPreference = requirePreference("publicFilters")
threadFiltersPreference = requirePreference("threadFilters")
notificationPreference.icon = IconicsDrawable(notificationPreference.context, GoogleMaterial.Icon.gmd_notifications).sizePx(iconSize).color(ThemeUtils.getColor(notificationPreference.context, R.attr.iconColor))
notificationPreference.icon = IconicsDrawable(notificationPreference.context, GoogleMaterial.Icon.gmd_notifications).apply { sizeRes = R.dimen.preference_icon_size; colorInt = ThemeUtils.getColor(notificationPreference.context, R.attr.iconColor) }
mutedUsersPreference.icon = getTintedIcon(R.drawable.ic_mute_24dp)
blockedUsersPreference.icon = IconicsDrawable(blockedUsersPreference.context, GoogleMaterial.Icon.gmd_block).sizePx(iconSize).color(ThemeUtils.getColor(blockedUsersPreference.context, R.attr.iconColor))
blockedUsersPreference.icon = IconicsDrawable(blockedUsersPreference.context, GoogleMaterial.Icon.gmd_block).apply { sizeRes = R.dimen.preference_icon_size; colorInt = ThemeUtils.getColor(blockedUsersPreference.context, R.attr.iconColor) }
mutedDomainsPreference.icon = getTintedIcon(R.drawable.ic_mute_24dp)
notificationPreference.onPreferenceClickListener = this

View File

@ -22,8 +22,10 @@ import com.keylesspalace.tusky.PreferencesActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.util.ThemeUtils
import com.keylesspalace.tusky.util.getNonNullString
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeRes
fun PreferenceFragmentCompat.requirePreference(key: String): Preference {
return findPreference(key)!!
@ -31,20 +33,18 @@ fun PreferenceFragmentCompat.requirePreference(key: String): Preference {
class PreferencesFragment : PreferenceFragmentCompat() {
private val iconSize by lazy {resources.getDimensionPixelSize(R.dimen.preference_icon_size)}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.preferences)
val themePreference: Preference = requirePreference("appTheme")
themePreference.icon = IconicsDrawable(themePreference.context, GoogleMaterial.Icon.gmd_palette).sizePx(iconSize).color(ThemeUtils.getColor(themePreference.context, R.attr.iconColor))
themePreference.icon = IconicsDrawable(themePreference.context, GoogleMaterial.Icon.gmd_palette).apply { sizeRes = R.dimen.preference_icon_size; colorInt = ThemeUtils.getColor(themePreference.context, R.attr.iconColor) }
val emojiPreference: Preference = requirePreference("emojiCompat")
emojiPreference.icon = IconicsDrawable(emojiPreference.context, GoogleMaterial.Icon.gmd_sentiment_satisfied).sizePx(iconSize).color(ThemeUtils.getColor(emojiPreference.context, R.attr.iconColor))
emojiPreference.icon = IconicsDrawable(emojiPreference.context, GoogleMaterial.Icon.gmd_sentiment_satisfied).apply { sizeRes = R.dimen.preference_icon_size; colorInt = ThemeUtils.getColor(themePreference.context, R.attr.iconColor) }
val textSizePreference: Preference = requirePreference("statusTextSize")
textSizePreference.icon = IconicsDrawable(textSizePreference.context, GoogleMaterial.Icon.gmd_format_size).sizePx(iconSize).color(ThemeUtils.getColor(textSizePreference.context, R.attr.iconColor))
textSizePreference.icon = IconicsDrawable(textSizePreference.context, GoogleMaterial.Icon.gmd_format_size).apply { sizeRes = R.dimen.preference_icon_size; colorInt = ThemeUtils.getColor(themePreference.context, R.attr.iconColor) }
val timelineFilterPreferences: Preference = requirePreference("timelineFilterPreferences")
timelineFilterPreferences.setOnPreferenceClickListener {
@ -67,7 +67,7 @@ class PreferencesFragment : PreferenceFragmentCompat() {
}
val languagePreference: Preference = requirePreference("language")
languagePreference.icon = IconicsDrawable(languagePreference.context, GoogleMaterial.Icon.gmd_translate).sizePx(iconSize).color(ThemeUtils.getColor(languagePreference.context, R.attr.iconColor))
languagePreference.icon = IconicsDrawable(languagePreference.context, GoogleMaterial.Icon.gmd_translate).apply { sizeRes = R.dimen.preference_icon_size; colorInt = ThemeUtils.getColor(themePreference.context, R.attr.iconColor) }
val botIndicatorPreference = requirePreference("showBotOverlay")

View File

@ -49,6 +49,7 @@ public class CustomEmojiHelper {
* @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
*/
@NonNull
public static Spanned emojifyText(@NonNull Spanned text, @Nullable List<Emoji> emojis, @NonNull final View view) {
if (emojis != null && !emojis.isEmpty()) {
@ -74,6 +75,7 @@ public class CustomEmojiHelper {
return text;
}
@NonNull
public static Spanned emojifyString(@NonNull String string, @Nullable List<Emoji> emojis, @NonNull final View ciew) {
return emojifyText(new SpannedString(string), emojis, ciew);
}
@ -81,7 +83,8 @@ public class CustomEmojiHelper {
public static class EmojiSpan extends ReplacementSpan {
private @Nullable Drawable imageDrawable;
@Nullable
private Drawable imageDrawable;
private WeakReference<View> viewWeakReference;
EmojiSpan(View view) {

View File

@ -21,7 +21,6 @@ import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
/**
* override BezelImageView from MaterialDrawer library to provide custom outline
*/
@ -39,7 +38,6 @@ public class BezelImageView extends com.mikepenz.materialdrawer.view.BezelImageV
super(context, attrs, defStyle);
}
@Override
protected void onSizeChanged(int w, int h, int old_w, int old_h) {
setOutlineProvider(new CustomOutline(w, h));

View File

@ -1,57 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:id="@+id/mainDrawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.keylesspalace.tusky.MainActivity">
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/main_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevationOverlayEnabled="false"
android:elevation="@dimen/actionbar_elevation">
<androidx.appcompat.widget.Toolbar
android:id="@+id/main_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentInsetStartWithNavigation="0dp">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
style="@style/TuskyTabAppearance"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:tabGravity="fill"
app:tabMaxWidth="0dp"
app:tabMode="fixed"
app:tabUnboundedRipple="false" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:background="?attr/windowBackgroundColor"
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/tab_layout"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
tools:context="com.keylesspalace.tusky.MainActivity">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floating_btn"
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="@dimen/actionbar_elevation"
app:elevationOverlayEnabled="false">
<androidx.appcompat.widget.Toolbar
android:id="@+id/mainToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentInsetStartWithNavigation="0dp">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
style="@style/TuskyTabAppearance"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:tabGravity="fill"
app:tabMaxWidth="0dp"
app:tabMode="fixed"
app:tabUnboundedRipple="false" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/tabLayout"
android:background="?attr/windowBackgroundColor"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/composeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:contentDescription="@string/action_compose"
app:layout_anchor="@id/viewPager"
app:layout_anchorGravity="bottom|end"
app:srcCompat="@drawable/ic_create_24dp" />
<include layout="@layout/item_status_bottom_sheet" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.mikepenz.materialdrawer.widget.MaterialDrawerSliderView
android:id="@+id/mainDrawer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:contentDescription="@string/action_compose"
app:layout_anchor="@id/pager"
app:layout_anchorGravity="bottom|end"
app:srcCompat="@drawable/ic_create_24dp" />
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true" />
<include layout="@layout/item_status_bottom_sheet" />
</androidx.drawerlayout.widget.DrawerLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,10 +1,12 @@
<!-- this replaces the default material_drawer_header.xml from the MaterialDrawer library -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<!-- this replaces the default material_drawer_header.xml from the MaterialDrawer library to enable rounded avatars -->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/material_drawer_account_header"
android:layout_width="match_parent"
android:layout_height="@dimen/material_drawer_account_header_height"
android:clickable="true">
android:clickable="true"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/material_drawer_account_header_background"
@ -25,64 +27,132 @@
<com.keylesspalace.tusky.view.BezelImageView
android:id="@+id/material_drawer_account_header_current"
style="@style/BezelImageView"
android:layout_width="@dimen/material_drawer_account_header_selected"
android:layout_height="@dimen/material_drawer_account_header_selected"
android:layout_marginStart="@dimen/material_drawer_vertical_padding"
android:layout_marginLeft="@dimen/material_drawer_vertical_padding"
android:layout_marginTop="@dimen/material_drawer_account_header_horizontal_top"
android:clickable="true"
android:elevation="8dp"
android:elevation="2dp"
android:focusable="true"
android:src="@drawable/avatar_default"
app:biv_maskDrawable="@drawable/materialdrawer_shape_large"
android:scaleType="fitCenter"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline"
app:materialDrawerMaskDrawable="@drawable/materialdrawer_shape_large" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/material_drawer_account_header_current_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="4dp"
android:fontFamily="sans-serif"
android:gravity="center"
android:lines="1"
android:minWidth="20dp"
android:paddingLeft="1dp"
android:paddingRight="1dp"
android:singleLine="true"
android:textSize="@dimen/material_drawer_item_badge_text"
app:layout_constraintBottom_toBottomOf="@id/material_drawer_account_header_current"
app:layout_constraintStart_toStartOf="@id/material_drawer_account_header_current"
tools:text="99" />
<com.keylesspalace.tusky.view.BezelImageView
android:id="@+id/material_drawer_account_header_small_first"
style="@style/BezelImageView"
android:layout_width="@dimen/material_drawer_account_header_secondary"
android:layout_height="@dimen/material_drawer_account_header_secondary"
android:layout_marginTop="@dimen/material_drawer_account_header_horizontal_top"
android:layout_marginEnd="@dimen/material_drawer_vertical_padding"
android:layout_marginRight="@dimen/material_drawer_vertical_padding"
android:clickable="true"
android:elevation="8dp"
android:elevation="2dp"
android:focusable="true"
android:src="@drawable/avatar_default"
app:biv_maskDrawable="@drawable/materialdrawer_shape_small"
android:scaleType="fitCenter"
android:visibility="visible"
app:layout_constraintEnd_toStartOf="@id/material_drawer_account_header_small_second"
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/material_drawer_account_header_small_first_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="4dp"
android:fontFamily="sans-serif"
android:gravity="center"
android:lines="1"
android:minWidth="20dp"
android:paddingLeft="1dp"
android:paddingRight="1dp"
android:singleLine="true"
android:textSize="@dimen/material_drawer_item_badge_small_text"
app:layout_constraintBottom_toBottomOf="@id/material_drawer_account_header_small_first"
app:layout_constraintStart_toStartOf="@id/material_drawer_account_header_small_first"
tools:text="99" />
<com.keylesspalace.tusky.view.BezelImageView
android:id="@+id/material_drawer_account_header_small_second"
style="@style/BezelImageView"
android:layout_width="@dimen/material_drawer_account_header_secondary"
android:layout_height="@dimen/material_drawer_account_header_secondary"
android:layout_marginTop="@dimen/material_drawer_account_header_horizontal_top"
android:layout_marginEnd="@dimen/material_drawer_vertical_padding"
android:layout_marginRight="@dimen/material_drawer_vertical_padding"
android:clickable="true"
android:elevation="8dp"
android:elevation="2dp"
android:focusable="true"
android:src="@drawable/avatar_default"
app:biv_maskDrawable="@drawable/materialdrawer_shape_small"
android:scaleType="fitCenter"
android:visibility="visible"
app:layout_constraintEnd_toStartOf="@id/material_drawer_account_header_small_third"
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/material_drawer_account_header_small_second_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="4dp"
android:fontFamily="sans-serif"
android:gravity="center"
android:lines="1"
android:minWidth="20dp"
android:paddingLeft="1dp"
android:paddingRight="1dp"
android:singleLine="true"
android:textSize="@dimen/material_drawer_item_badge_small_text"
app:layout_constraintBottom_toBottomOf="@id/material_drawer_account_header_small_second"
app:layout_constraintStart_toStartOf="@id/material_drawer_account_header_small_second"
tools:text="99" />
<com.keylesspalace.tusky.view.BezelImageView
android:id="@+id/material_drawer_account_header_small_third"
style="@style/BezelImageView"
android:layout_width="@dimen/material_drawer_account_header_secondary"
android:layout_height="@dimen/material_drawer_account_header_secondary"
android:layout_marginTop="@dimen/material_drawer_account_header_horizontal_top"
android:layout_marginEnd="@dimen/material_drawer_vertical_padding"
android:layout_marginRight="@dimen/material_drawer_vertical_padding"
android:clickable="true"
android:elevation="8dp"
android:elevation="2dp"
android:focusable="true"
android:src="@drawable/avatar_default"
app:biv_maskDrawable="@drawable/materialdrawer_shape_small"
android:scaleType="fitCenter"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/material_drawer_account_header_small_third_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="4dp"
android:fontFamily="sans-serif"
android:gravity="center"
android:lines="1"
android:minWidth="20dp"
android:paddingLeft="1dp"
android:paddingRight="1dp"
android:singleLine="true"
android:textSize="@dimen/material_drawer_item_badge_small_text"
app:layout_constraintBottom_toBottomOf="@id/material_drawer_account_header_small_third"
app:layout_constraintStart_toStartOf="@id/material_drawer_account_header_small_third"
tools:text="99" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/material_drawer_text_guideline"
android:layout_width="wrap_content"
@ -95,7 +165,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/material_drawer_vertical_padding"
android:layout_marginLeft="@dimen/material_drawer_vertical_padding"
android:fontFamily="sans-serif-medium"
android:lines="1"
android:maxLines="1"
@ -111,7 +180,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/material_drawer_vertical_padding"
android:layout_marginLeft="@dimen/material_drawer_vertical_padding"
android:layout_marginBottom="@dimen/material_drawer_padding"
android:fontFamily="sans-serif"
android:lines="1"
@ -127,8 +195,7 @@
android:layout_width="@dimen/material_drawer_account_header_dropdown"
android:layout_height="@dimen/material_drawer_account_header_dropdown"
android:layout_marginEnd="@dimen/material_drawer_vertical_padding"
android:layout_marginRight="@dimen/material_drawer_vertical_padding"
android:layout_marginBottom="@dimen/material_drawer_account_header_dropdown_margin_bottom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</merge>

View File

@ -19,6 +19,8 @@
<color name="favoriteButtonActiveColor">@color/tusky_orange</color>
<color name="headerBackgroundFilter">@color/header_background_filter_dark</color>
<bool name="lightNavigationBar">false</bool>
</resources>

View File

@ -26,7 +26,9 @@
<color name="transparent_tusky_blue">#8c2b90d9</color>
<color name="transparent_black">#8f000000</color>
<color name="header_background_filter">#44000000</color>
<color name="header_background_filter_dark">#44000000</color>
<color name="header_background_filter_light">#66FFFFFF</color>
<color name="transparent_statusbar_background">#44000000</color>
<!-- colors used in the elephant friend drawables -->
<color name="elephant_friend_border_color">#121419</color>

View File

@ -67,14 +67,8 @@
<item name="textColorDisabled">@color/textColorDisabled</item>
<item name="material_drawer_background">@color/colorBackground</item>
<item name="material_drawer_primary_text">@color/textColorSecondary</item>
<item name="material_drawer_primary_icon">@color/iconColor</item>
<item name="material_drawer_secondary_text">@color/textColorTertiary</item>
<item name="material_drawer_hint_text">@color/textColorTertiary</item>
<item name="material_drawer_divider">?attr/dividerColor</item>
<item name="material_drawer_header_selection_text">@color/white</item>
<item name="material_drawer_header_selection_subtext">@color/white</item>
<item name="materialDrawerStyle">@style/TuskyDrawerStyle</item>
<item name="materialDrawerHeaderStyle">@style/TuskyDrawerHeaderStyle</item>
<item name="alertDialogTheme">@style/TuskyDialog</item>
<item name="snackbarButtonStyle">@style/TuskyButton.TextButton</item>
@ -142,17 +136,26 @@
<item name="colorBackgroundAccent">@color/tusky_grey_20</item>
<item name="dividerColor">@color/tusky_grey_10</item>
<item name="material_drawer_background">@color/black</item>
<item name="material_drawer_primary_icon">@color/tusky_grey_40</item>
</style>
<style name="TuskyBlackTheme" parent="TuskyBlackThemeBase" />
<style name="TuskyDrawerStyle" parent ="Widget.MaterialDrawerStyle">
<item name="materialDrawerBackground">?android:colorBackground</item>
<item name="materialDrawerPrimaryIcon">?iconColor</item>
<item name="materialDrawerSecondaryIcon">?iconColor</item>
<item name="materialDrawerDividerColor">?dividerColor</item>
</style>
<style name="TuskyDrawerHeaderStyle" parent ="Widget.MaterialDrawerHeaderStyle">
<item name="materialDrawerHeaderSelectionText">?android:textColorPrimary</item>
<item name="materialDrawerHeaderSelectionSubtext">?android:textColorPrimary</item>
</style>
<!-- customize the shape of the avatars in account selection list -->
<style name="BezelImageView">
<item name="biv_maskDrawable">@drawable/materialdrawer_shape_small</item>
<item name="biv_drawCircularShadow">false</item>
<item name="materialDrawerMaskDrawable">@drawable/materialdrawer_shape_small</item>
<item name="materialDrawerDrawCircularShadow">false</item>
</style>
</resources>

View File

@ -19,6 +19,8 @@
<color name="favoriteButtonActiveColor">@color/tusky_orange_light</color>
<color name="headerBackgroundFilter">@color/header_background_filter_light</color>
<bool name="lightNavigationBar">true</bool>
</resources>

View File

@ -115,7 +115,7 @@ class FilterTest {
activity.supportFragmentManager.beginTransaction()
.replace(R.id.activity_main, fragment, "fragment")
.replace(R.id.mainDrawerLayout, fragment, "fragment")
.commit()
fragment.reloadFilters(false)