Animate gif avatars (#1279)

* animate gif avatars

* add setting to enable avatar animation

* cleanup code
This commit is contained in:
Konrad Pozniak 2019-05-26 08:46:08 +02:00 committed by GitHub
parent df401e90b0
commit fb45e0e2bb
40 changed files with 381 additions and 547 deletions

View File

@ -78,6 +78,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
private var showingReblogs: Boolean = false private var showingReblogs: Boolean = false
private var loadedAccount: Account? = null private var loadedAccount: Account? = null
private var animateAvatar: Boolean = false
// fields for scroll animation // fields for scroll animation
private var hideFab: Boolean = false private var hideFab: Boolean = false
private var oldOffset: Int = 0 private var oldOffset: Int = 0
@ -120,7 +122,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
updateButtons() updateButtons()
} }
hideFab = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("fabHide", false) val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)
animateAvatar = sharedPrefs.getBoolean("animateGifAvatars", false)
hideFab = sharedPrefs.getBoolean("fabHide", false)
loadResources() loadResources()
setupToolbar() setupToolbar()
@ -379,11 +383,16 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
*/ */
private fun updateAccountAvatar() { private fun updateAccountAvatar() {
loadedAccount?.let { account -> loadedAccount?.let { account ->
loadAvatar(
account.avatar,
accountAvatarImageView,
resources.getDimensionPixelSize(R.dimen.avatar_radius_94dp),
animateAvatar
)
Glide.with(this) Glide.with(this)
.load(account.avatar) .asBitmap()
.placeholder(R.drawable.avatar_default)
.into(accountAvatarImageView)
Glide.with(this)
.load(account.header) .load(account.header)
.centerCrop() .centerCrop()
.into(accountHeaderImageView) .into(accountHeaderImageView)
@ -430,10 +439,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
accountMovedDisplayName.text = movedAccount.name accountMovedDisplayName.text = movedAccount.name
accountMovedUsername.text = getString(R.string.status_username_format, movedAccount.username) accountMovedUsername.text = getString(R.string.status_username_format, movedAccount.username)
Glide.with(this) val avatarRadius = resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp)
.load(movedAccount.avatar)
.placeholder(R.drawable.avatar_default) loadAvatar(movedAccount.avatar, accountMovedAvatar, avatarRadius, animateAvatar)
.into(accountMovedAvatar)
accountMovedText.text = getString(R.string.account_moved_description, movedAccount.displayName) accountMovedText.text = getString(R.string.account_moved_description, movedAccount.displayName)

View File

@ -26,6 +26,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor; import android.content.res.AssetFileDescriptor;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
@ -60,25 +61,6 @@ import android.widget.PopupMenu;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.core.view.inputmethod.InputConnectionCompat;
import androidx.core.view.inputmethod.InputContentInfoCompat;
import androidx.lifecycle.Lifecycle;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.transition.TransitionManager;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
@ -103,6 +85,7 @@ import com.keylesspalace.tusky.service.SendTootService;
import com.keylesspalace.tusky.util.ComposeTokenizer; import com.keylesspalace.tusky.util.ComposeTokenizer;
import com.keylesspalace.tusky.util.CountUpDownLatch; import com.keylesspalace.tusky.util.CountUpDownLatch;
import com.keylesspalace.tusky.util.DownsizeImageTask; import com.keylesspalace.tusky.util.DownsizeImageTask;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
import com.keylesspalace.tusky.util.ListUtils; import com.keylesspalace.tusky.util.ListUtils;
import com.keylesspalace.tusky.util.SaveTootHelper; import com.keylesspalace.tusky.util.SaveTootHelper;
import com.keylesspalace.tusky.util.SpanUtilsKt; import com.keylesspalace.tusky.util.SpanUtilsKt;
@ -303,14 +286,20 @@ public final class ComposeActivity
if (activeAccount != null) { if (activeAccount != null) {
ImageView composeAvatar = findViewById(R.id.composeAvatar); ImageView composeAvatar = findViewById(R.id.composeAvatar);
if (TextUtils.isEmpty(activeAccount.getProfilePictureUrl())) {
composeAvatar.setImageResource(R.drawable.avatar_default); int[] actionBarSizeAttr = new int[] { R.attr.actionBarSize };
} else { TypedArray a = obtainStyledAttributes(null, actionBarSizeAttr);
Glide.with(this).load(activeAccount.getProfilePictureUrl()) int avatarSize = a.getDimensionPixelSize(0, 1);
.error(R.drawable.avatar_default) a.recycle();
.placeholder(R.drawable.avatar_default)
.into(composeAvatar); boolean animateAvatars = preferences.getBoolean("animateGifAvatars", false);
}
ImageLoadingHelper.loadAvatar(
activeAccount.getProfilePictureUrl(),
composeAvatar,
avatarSize / 8,
animateAvatars
);
composeAvatar.setContentDescription( composeAvatar.setContentDescription(
getString(R.string.compose_active_account_description, getString(R.string.compose_active_account_description,

View File

@ -35,6 +35,8 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.FitCenter
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.keylesspalace.tusky.adapter.AccountFieldEditAdapter import com.keylesspalace.tusky.adapter.AccountFieldEditAdapter
import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.di.ViewModelFactory
@ -136,6 +138,10 @@ class EditProfileActivity : BaseActivity(), Injectable {
Glide.with(this) Glide.with(this)
.load(me.avatar) .load(me.avatar)
.placeholder(R.drawable.avatar_default) .placeholder(R.drawable.avatar_default)
.transform(
FitCenter(),
RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp))
)
.into(avatarPreview) .into(avatarPreview)
} }
@ -158,8 +164,8 @@ class EditProfileActivity : BaseActivity(), Injectable {
} }
}) })
observeImage(viewModel.avatarData, avatarPreview, avatarProgressBar) observeImage(viewModel.avatarData, avatarPreview, avatarProgressBar, true)
observeImage(viewModel.headerData, headerPreview, headerProgressBar) observeImage(viewModel.headerData, headerPreview, headerProgressBar, false)
viewModel.saveData.observe(this, Observer<Resource<Nothing>> { viewModel.saveData.observe(this, Observer<Resource<Nothing>> {
when(it) { when(it) {
@ -192,12 +198,26 @@ class EditProfileActivity : BaseActivity(), Injectable {
} }
} }
private fun observeImage(liveData: LiveData<Resource<Bitmap>>, imageView: ImageView, progressBar: View) { private fun observeImage(liveData: LiveData<Resource<Bitmap>>,
imageView: ImageView,
progressBar: View,
roundedCorners: Boolean) {
liveData.observe(this, Observer<Resource<Bitmap>> { liveData.observe(this, Observer<Resource<Bitmap>> {
when (it) { when (it) {
is Success -> { is Success -> {
imageView.setImageBitmap(it.data) val glide = Glide.with(imageView)
.load(it.data)
if (roundedCorners) {
glide.transform(
FitCenter(),
RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp))
)
}
glide.into(imageView)
imageView.show() imageView.show()
progressBar.hide() progressBar.hide()
} }

View File

@ -36,6 +36,7 @@ import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import android.os.Handler; import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.widget.ImageButton; import android.widget.ImageButton;
@ -78,9 +79,6 @@ import dagger.android.AndroidInjector;
import dagger.android.DispatchingAndroidInjector; import dagger.android.DispatchingAndroidInjector;
import dagger.android.support.HasSupportFragmentInjector; import dagger.android.support.HasSupportFragmentInjector;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import static com.keylesspalace.tusky.util.MediaUtilsKt.deleteStaleCachedMedia; import static com.keylesspalace.tusky.util.MediaUtilsKt.deleteStaleCachedMedia;
import static com.uber.autodispose.AutoDispose.autoDisposable; import static com.uber.autodispose.AutoDispose.autoDisposable;
@ -330,9 +328,18 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
background.setColorFilter(ContextCompat.getColor(this, R.color.header_background_filter)); background.setColorFilter(ContextCompat.getColor(this, R.color.header_background_filter));
background.setBackgroundColor(ContextCompat.getColor(this, R.color.window_background_dark)); background.setBackgroundColor(ContextCompat.getColor(this, R.color.window_background_dark));
final boolean animateAvatars = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("animateGifAvatars", false);
DrawerImageLoader.init(new AbstractDrawerImageLoader() { DrawerImageLoader.init(new AbstractDrawerImageLoader() {
@Override @Override
public void set(ImageView imageView, Uri uri, Drawable placeholder, String tag) { 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) Glide.with(MainActivity.this)
.asBitmap() .asBitmap()
.load(uri) .load(uri)
@ -340,6 +347,8 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
.into(imageView); .into(imageView);
} }
}
@Override @Override
public void cancel(ImageView imageView) { public void cancel(ImageView imageView) {
Glide.with(MainActivity.this).clear(imageView); Glide.with(MainActivity.this).clear(imageView);

View File

@ -137,13 +137,7 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference
} }
//workaround end //workaround end
} }
"statusTextSize" -> { "statusTextSize", "absoluteTimeView", "showBotOverlay", "animateGifAvatars" -> {
restartActivitiesOnExit = true
}
"absoluteTimeView" -> {
restartActivitiesOnExit = true
}
"showBotOverlay" -> {
restartActivitiesOnExit = true restartActivitiesOnExit = true
} }
"language" -> { "language" -> {

View File

@ -16,19 +16,19 @@
package com.keylesspalace.tusky.adapter package com.keylesspalace.tusky.adapter
import android.content.Context import android.content.Context
import android.text.TextUtils import android.preference.PreferenceManager
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import com.bumptech.glide.Glide
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.util.CustomEmojiHelper import com.keylesspalace.tusky.util.CustomEmojiHelper
import com.keylesspalace.tusky.util.loadAvatar
import kotlinx.android.synthetic.main.item_autocomplete_account.view.* import kotlinx.android.synthetic.main.item_autocomplete_account.view.*
class AccountSelectionAdapter(context: Context): ArrayAdapter<AccountEntity>(context, R.layout.item_autocomplete_account) { class AccountSelectionAdapter(context: Context) : ArrayAdapter<AccountEntity>(context, R.layout.item_autocomplete_account) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var view = convertView var view = convertView
@ -45,13 +45,13 @@ class AccountSelectionAdapter(context: Context): ArrayAdapter<AccountEntity>(con
val avatar = view.avatar val avatar = view.avatar
username.text = account.fullName username.text = account.fullName
displayName.text = CustomEmojiHelper.emojifyString(account.displayName, account.emojis, displayName) displayName.text = CustomEmojiHelper.emojifyString(account.displayName, account.emojis, displayName)
if (!TextUtils.isEmpty(account.profilePictureUrl)) {
Glide.with(avatar) val avatarRadius = avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_42dp)
.asBitmap() val animateAvatar = PreferenceManager.getDefaultSharedPreferences(avatar.context)
.load(account.profilePictureUrl) .getBoolean("animateGifAvatars", false)
.placeholder(R.drawable.avatar_default)
.into(avatar) loadAvatar(account.profilePictureUrl, avatar, avatarRadius, animateAvatar)
}
} }
return view return view

View File

@ -2,17 +2,18 @@ package com.keylesspalace.tusky.adapter;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener; import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.keylesspalace.tusky.interfaces.LinkListener; import com.keylesspalace.tusky.interfaces.LinkListener;
import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
class AccountViewHolder extends RecyclerView.ViewHolder { class AccountViewHolder extends RecyclerView.ViewHolder {
private TextView username; private TextView username;
@ -21,6 +22,7 @@ class AccountViewHolder extends RecyclerView.ViewHolder {
private ImageView avatarInset; private ImageView avatarInset;
private String accountId; private String accountId;
private boolean showBotOverlay; private boolean showBotOverlay;
private boolean animateAvatar;
AccountViewHolder(View itemView) { AccountViewHolder(View itemView) {
super(itemView); super(itemView);
@ -28,7 +30,9 @@ class AccountViewHolder extends RecyclerView.ViewHolder {
displayName = itemView.findViewById(R.id.account_display_name); displayName = itemView.findViewById(R.id.account_display_name);
avatar = itemView.findViewById(R.id.account_avatar); avatar = itemView.findViewById(R.id.account_avatar);
avatarInset = itemView.findViewById(R.id.account_avatar_inset); avatarInset = itemView.findViewById(R.id.account_avatar_inset);
showBotOverlay = PreferenceManager.getDefaultSharedPreferences(itemView.getContext()).getBoolean("showBotOverlay", true); SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(itemView.getContext());
showBotOverlay = sharedPrefs.getBoolean("showBotOverlay", true);
animateAvatar = sharedPrefs.getBoolean("animateGifAvatars", false);
} }
void setupWithAccount(Account account) { void setupWithAccount(Account account) {
@ -38,11 +42,9 @@ class AccountViewHolder extends RecyclerView.ViewHolder {
username.setText(formattedUsername); username.setText(formattedUsername);
CharSequence emojifiedName = CustomEmojiHelper.emojifyString(account.getName(), account.getEmojis(), displayName); CharSequence emojifiedName = CustomEmojiHelper.emojifyString(account.getName(), account.getEmojis(), displayName);
displayName.setText(emojifiedName); displayName.setText(emojifiedName);
Glide.with(avatar) int avatarRadius = avatar.getContext().getResources()
.asBitmap() .getDimensionPixelSize(R.dimen.avatar_radius_48dp);
.load(account.getAvatar()) ImageLoadingHelper.loadAvatar(account.getAvatar(), avatar, avatarRadius, animateAvatar);
.placeholder(R.drawable.avatar_default)
.into(avatar);
if (showBotOverlay && account.getBot()) { if (showBotOverlay && account.getBot()) {
avatarInset.setVisibility(View.VISIBLE); avatarInset.setVisibility(View.VISIBLE);
avatarInset.setImageResource(R.drawable.ic_bot_24dp); avatarInset.setImageResource(R.drawable.ic_bot_24dp);

View File

@ -17,6 +17,8 @@ package com.keylesspalace.tusky.adapter;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import android.preference.PreferenceManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -24,11 +26,11 @@ import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener; import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
public class BlocksAdapter extends AccountAdapter { public class BlocksAdapter extends AccountAdapter {
@ -69,6 +71,7 @@ public class BlocksAdapter extends AccountAdapter {
private TextView displayName; private TextView displayName;
private ImageButton unblock; private ImageButton unblock;
private String id; private String id;
private boolean animateAvatar;
BlockedUserViewHolder(View itemView) { BlockedUserViewHolder(View itemView) {
super(itemView); super(itemView);
@ -76,6 +79,9 @@ public class BlocksAdapter extends AccountAdapter {
username = itemView.findViewById(R.id.blocked_user_username); username = itemView.findViewById(R.id.blocked_user_username);
displayName = itemView.findViewById(R.id.blocked_user_display_name); displayName = itemView.findViewById(R.id.blocked_user_display_name);
unblock = itemView.findViewById(R.id.blocked_user_unblock); unblock = itemView.findViewById(R.id.blocked_user_unblock);
animateAvatar = PreferenceManager.getDefaultSharedPreferences(itemView.getContext())
.getBoolean("animateGifAvatars", false);
} }
void setupWithAccount(Account account) { void setupWithAccount(Account account) {
@ -85,11 +91,9 @@ public class BlocksAdapter extends AccountAdapter {
String format = username.getContext().getString(R.string.status_username_format); String format = username.getContext().getString(R.string.status_username_format);
String formattedUsername = String.format(format, account.getUsername()); String formattedUsername = String.format(format, account.getUsername());
username.setText(formattedUsername); username.setText(formattedUsername);
Glide.with(avatar) int avatarRadius = avatar.getContext().getResources()
.asBitmap() .getDimensionPixelSize(R.dimen.avatar_radius_48dp);
.load(account.getAvatar()) ImageLoadingHelper.loadAvatar(account.getAvatar(), avatar, avatarRadius, animateAvatar);
.placeholder(R.drawable.avatar_default)
.into(avatar);
} }
void setupActionListener(final AccountActionListener listener) { void setupActionListener(final AccountActionListener listener) {

View File

@ -16,6 +16,7 @@
package com.keylesspalace.tusky.adapter; package com.keylesspalace.tusky.adapter;
import android.content.Context; import android.content.Context;
import android.preference.PreferenceManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -30,6 +31,7 @@ import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.Emoji; import com.keylesspalace.tusky.entity.Emoji;
import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -146,13 +148,19 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter
CharSequence emojifiedName = CustomEmojiHelper.emojifyString(account.getName(), CharSequence emojifiedName = CustomEmojiHelper.emojifyString(account.getName(),
account.getEmojis(), accountViewHolder.displayName); account.getEmojis(), accountViewHolder.displayName);
accountViewHolder.displayName.setText(emojifiedName); accountViewHolder.displayName.setText(emojifiedName);
if (!account.getAvatar().isEmpty()) {
Glide.with(accountViewHolder.avatar) int avatarRadius = accountViewHolder.avatar.getContext().getResources()
.asBitmap() .getDimensionPixelSize(R.dimen.avatar_radius_42dp);
.load(account.getAvatar())
.placeholder(R.drawable.avatar_default) boolean animateAvatar = PreferenceManager.getDefaultSharedPreferences(accountViewHolder.avatar.getContext())
.into(accountViewHolder.avatar); .getBoolean("animateGifAvatars", false);
}
ImageLoadingHelper.loadAvatar(
account.getAvatar(),
accountViewHolder.avatar,
avatarRadius,
animateAvatar
);
} }
break; break;

View File

@ -17,6 +17,8 @@ package com.keylesspalace.tusky.adapter;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import android.preference.PreferenceManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -24,11 +26,11 @@ import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener; import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
public class FollowRequestsAdapter extends AccountAdapter { public class FollowRequestsAdapter extends AccountAdapter {
@ -70,6 +72,7 @@ public class FollowRequestsAdapter extends AccountAdapter {
private ImageButton accept; private ImageButton accept;
private ImageButton reject; private ImageButton reject;
private String id; private String id;
private boolean animateAvatar;
FollowRequestViewHolder(View itemView) { FollowRequestViewHolder(View itemView) {
super(itemView); super(itemView);
@ -78,6 +81,8 @@ public class FollowRequestsAdapter extends AccountAdapter {
displayName = itemView.findViewById(R.id.displayNameTextView); displayName = itemView.findViewById(R.id.displayNameTextView);
accept = itemView.findViewById(R.id.acceptButton); accept = itemView.findViewById(R.id.acceptButton);
reject = itemView.findViewById(R.id.rejectButton); reject = itemView.findViewById(R.id.rejectButton);
animateAvatar = PreferenceManager.getDefaultSharedPreferences(itemView.getContext())
.getBoolean("animateGifAvatars", false);
} }
void setupWithAccount(Account account) { void setupWithAccount(Account account) {
@ -87,11 +92,9 @@ public class FollowRequestsAdapter extends AccountAdapter {
String format = username.getContext().getString(R.string.status_username_format); String format = username.getContext().getString(R.string.status_username_format);
String formattedUsername = String.format(format, account.getUsername()); String formattedUsername = String.format(format, account.getUsername());
username.setText(formattedUsername); username.setText(formattedUsername);
Glide.with(avatar) int avatarRadius = avatar.getContext().getResources()
.asBitmap() .getDimensionPixelSize(R.dimen.avatar_radius_48dp);
.load(account.getAvatar()) ImageLoadingHelper.loadAvatar(account.getAvatar(), avatar, avatarRadius, animateAvatar);
.placeholder(R.drawable.avatar_default)
.into(avatar);
} }
void setupActionListener(final AccountActionListener listener) { void setupActionListener(final AccountActionListener listener) {

View File

@ -2,6 +2,8 @@ package com.keylesspalace.tusky.adapter;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import android.preference.PreferenceManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -9,11 +11,11 @@ import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener; import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
public class MutesAdapter extends AccountAdapter { public class MutesAdapter extends AccountAdapter {
@ -55,6 +57,7 @@ public class MutesAdapter extends AccountAdapter {
private TextView displayName; private TextView displayName;
private ImageButton unmute; private ImageButton unmute;
private String id; private String id;
private boolean animateAvatar;
MutedUserViewHolder(View itemView) { MutedUserViewHolder(View itemView) {
super(itemView); super(itemView);
@ -62,6 +65,8 @@ public class MutesAdapter extends AccountAdapter {
username = itemView.findViewById(R.id.muted_user_username); username = itemView.findViewById(R.id.muted_user_username);
displayName = itemView.findViewById(R.id.muted_user_display_name); displayName = itemView.findViewById(R.id.muted_user_display_name);
unmute = itemView.findViewById(R.id.muted_user_unmute); unmute = itemView.findViewById(R.id.muted_user_unmute);
animateAvatar = PreferenceManager.getDefaultSharedPreferences(itemView.getContext())
.getBoolean("animateGifAvatars", false);
} }
void setupWithAccount(Account account) { void setupWithAccount(Account account) {
@ -71,11 +76,9 @@ public class MutesAdapter extends AccountAdapter {
String format = username.getContext().getString(R.string.status_username_format); String format = username.getContext().getString(R.string.status_username_format);
String formattedUsername = String.format(format, account.getUsername()); String formattedUsername = String.format(format, account.getUsername());
username.setText(formattedUsername); username.setText(formattedUsername);
Glide.with(avatar) int avatarRadius = avatar.getContext().getResources()
.asBitmap() .getDimensionPixelSize(R.dimen.avatar_radius_48dp);
.load(account.getAvatar()) ImageLoadingHelper.loadAvatar(account.getAvatar(), avatar, avatarRadius, animateAvatar);
.placeholder(R.drawable.avatar_default)
.into(avatar);
} }
void setupActionListener(final AccountActionListener listener) { void setupActionListener(final AccountActionListener listener) {

View File

@ -33,7 +33,6 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.ToggleButton; import android.widget.ToggleButton;
import com.bumptech.glide.Glide;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.Emoji; import com.keylesspalace.tusky.entity.Emoji;
@ -42,6 +41,7 @@ import com.keylesspalace.tusky.interfaces.LinkListener;
import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.DateUtils; import com.keylesspalace.tusky.util.DateUtils;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
import com.keylesspalace.tusky.util.LinkHelper; import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.util.SmartLengthInputFilter; import com.keylesspalace.tusky.util.SmartLengthInputFilter;
import com.keylesspalace.tusky.viewdata.NotificationViewData; import com.keylesspalace.tusky.viewdata.NotificationViewData;
@ -82,6 +82,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private NotificationActionListener notificationActionListener; private NotificationActionListener notificationActionListener;
private boolean mediaPreviewEnabled; private boolean mediaPreviewEnabled;
private boolean useAbsoluteTime; private boolean useAbsoluteTime;
private boolean showBotOverlay;
private boolean animateAvatar;
private BidiFormatter bidiFormatter; private BidiFormatter bidiFormatter;
private AdapterDataSource<NotificationViewData> dataSource; private AdapterDataSource<NotificationViewData> dataSource;
@ -96,6 +98,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
this.notificationActionListener = notificationActionListener; this.notificationActionListener = notificationActionListener;
mediaPreviewEnabled = true; mediaPreviewEnabled = true;
useAbsoluteTime = false; useAbsoluteTime = false;
showBotOverlay = true;
animateAvatar = false;
bidiFormatter = BidiFormatter.getInstance(); bidiFormatter = BidiFormatter.getInstance();
} }
@ -112,12 +116,12 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
case VIEW_TYPE_STATUS_NOTIFICATION: { case VIEW_TYPE_STATUS_NOTIFICATION: {
View view = inflater View view = inflater
.inflate(R.layout.item_status_notification, parent, false); .inflate(R.layout.item_status_notification, parent, false);
return new StatusNotificationViewHolder(view, useAbsoluteTime); return new StatusNotificationViewHolder(view, useAbsoluteTime, animateAvatar);
} }
case VIEW_TYPE_FOLLOW: { case VIEW_TYPE_FOLLOW: {
View view = inflater View view = inflater
.inflate(R.layout.item_follow, parent, false); .inflate(R.layout.item_follow, parent, false);
return new FollowViewHolder(view); return new FollowViewHolder(view, animateAvatar);
} }
case VIEW_TYPE_PLACEHOLDER: { case VIEW_TYPE_PLACEHOLDER: {
View view = inflater View view = inflater
@ -167,7 +171,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
StatusViewHolder holder = (StatusViewHolder) viewHolder; StatusViewHolder holder = (StatusViewHolder) viewHolder;
StatusViewData.Concrete status = concreteNotificaton.getStatusViewData(); StatusViewData.Concrete status = concreteNotificaton.getStatusViewData();
holder.setupWithStatus(status, holder.setupWithStatus(status,
statusListener, mediaPreviewEnabled, payloadForHolder); statusListener, mediaPreviewEnabled, showBotOverlay, animateAvatar, payloadForHolder);
if(concreteNotificaton.getType() == Notification.Type.POLL) { if(concreteNotificaton.getType() == Notification.Type.POLL) {
holder.setPollInfo(accountId.equals(concreteNotificaton.getAccount().getId())); holder.setPollInfo(accountId.equals(concreteNotificaton.getAccount().getId()));
} else { } else {
@ -266,6 +270,14 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
this.useAbsoluteTime = useAbsoluteTime; this.useAbsoluteTime = useAbsoluteTime;
} }
public void setShowBotOverlay(boolean showBotOverlay) {
this.showBotOverlay = showBotOverlay;
}
public void setAnimateAvatar(boolean animateAvatar) {
this.animateAvatar = animateAvatar;
}
public interface NotificationActionListener { public interface NotificationActionListener {
void onViewAccount(String id); void onViewAccount(String id);
@ -288,13 +300,15 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private TextView usernameView; private TextView usernameView;
private TextView displayNameView; private TextView displayNameView;
private ImageView avatar; private ImageView avatar;
private boolean animateAvatar;
FollowViewHolder(View itemView) { FollowViewHolder(View itemView, boolean animateAvatar) {
super(itemView); super(itemView);
message = itemView.findViewById(R.id.notification_text); message = itemView.findViewById(R.id.notification_text);
usernameView = itemView.findViewById(R.id.notification_username); usernameView = itemView.findViewById(R.id.notification_username);
displayNameView = itemView.findViewById(R.id.notification_display_name); displayNameView = itemView.findViewById(R.id.notification_display_name);
avatar = itemView.findViewById(R.id.notification_avatar); avatar = itemView.findViewById(R.id.notification_avatar);
this.animateAvatar = animateAvatar;
} }
void setMessage(Account account, BidiFormatter bidiFormatter) { void setMessage(Account account, BidiFormatter bidiFormatter) {
@ -313,15 +327,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
displayNameView.setText(emojifiedDisplayName); displayNameView.setText(emojifiedDisplayName);
if (TextUtils.isEmpty(account.getAvatar())) { int avatarRadius = avatar.getContext().getResources()
avatar.setImageResource(R.drawable.avatar_default); .getDimensionPixelSize(R.dimen.avatar_radius_24dp);
} else {
Glide.with(avatar) ImageLoadingHelper.loadAvatar(account.getAvatar(), avatar, avatarRadius, animateAvatar);
.asBitmap()
.load(account.getAvatar())
.placeholder(R.drawable.avatar_default)
.into(avatar);
}
} }
void setupButtons(final NotificationActionListener listener, final String accountId) { void setupButtons(final NotificationActionListener listener, final String accountId) {
@ -349,10 +359,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private StatusViewData.Concrete statusViewData; private StatusViewData.Concrete statusViewData;
private boolean useAbsoluteTime; private boolean useAbsoluteTime;
private boolean animateAvatar;
private SimpleDateFormat shortSdf; private SimpleDateFormat shortSdf;
private SimpleDateFormat longSdf; private SimpleDateFormat longSdf;
StatusNotificationViewHolder(View itemView, boolean useAbsoluteTime) { StatusNotificationViewHolder(View itemView, boolean useAbsoluteTime, boolean animateAvatar) {
super(itemView); super(itemView);
message = itemView.findViewById(R.id.notification_top_text); message = itemView.findViewById(R.id.notification_top_text);
statusNameBar = itemView.findViewById(R.id.status_name_bar); statusNameBar = itemView.findViewById(R.id.status_name_bar);
@ -376,6 +387,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
contentWarningButton.setOnCheckedChangeListener(this); contentWarningButton.setOnCheckedChangeListener(this);
this.useAbsoluteTime = useAbsoluteTime; this.useAbsoluteTime = useAbsoluteTime;
this.animateAvatar = animateAvatar;
shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault()); longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault());
} }
@ -495,23 +507,17 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
void setAvatars(@Nullable String statusAvatarUrl, @Nullable String notificationAvatarUrl) { void setAvatars(@Nullable String statusAvatarUrl, @Nullable String notificationAvatarUrl) {
if (TextUtils.isEmpty(statusAvatarUrl)) { int statusAvatarRadius = statusAvatar.getContext().getResources()
statusAvatar.setImageResource(R.drawable.avatar_default); .getDimensionPixelSize(R.dimen.avatar_radius_48dp);
} else {
Glide.with(statusAvatar)
.load(statusAvatarUrl)
.placeholder(R.drawable.avatar_default)
.into(statusAvatar);
}
if (TextUtils.isEmpty(notificationAvatarUrl)) { ImageLoadingHelper.loadAvatar(statusAvatarUrl,
notificationAvatar.setImageResource(R.drawable.avatar_default); statusAvatar, statusAvatarRadius, animateAvatar);
} else {
Glide.with(notificationAvatar) int notificationAvatarRadius = statusAvatar.getContext().getResources()
.load(notificationAvatarUrl) .getDimensionPixelSize(R.dimen.avatar_radius_24dp);
.placeholder(R.drawable.avatar_default)
.into(notificationAvatar); ImageLoadingHelper.loadAvatar(notificationAvatarUrl,
} notificationAvatar, notificationAvatarRadius, animateAvatar);
} }
@Override @Override

View File

@ -49,15 +49,19 @@ public class SearchResultsAdapter extends RecyclerView.Adapter {
private boolean mediaPreviewsEnabled; private boolean mediaPreviewsEnabled;
private boolean alwaysShowSensitiveMedia; private boolean alwaysShowSensitiveMedia;
private boolean useAbsoluteTime; private boolean useAbsoluteTime;
private boolean showBotOverlay;
private boolean animateAvatar;
private LinkListener linkListener; private LinkListener linkListener;
private StatusActionListener statusListener; private StatusActionListener statusListener;
public SearchResultsAdapter(boolean mediaPreviewsEnabled, public SearchResultsAdapter(LinkListener linkListener,
boolean alwaysShowSensitiveMedia,
LinkListener linkListener,
StatusActionListener statusListener, StatusActionListener statusListener,
boolean useAbsoluteTime) { boolean mediaPreviewsEnabled,
boolean alwaysShowSensitiveMedia,
boolean useAbsoluteTime,
boolean showBotOverlay,
boolean animateAvatar) {
this.accountList = Collections.emptyList(); this.accountList = Collections.emptyList();
this.statusList = Collections.emptyList(); this.statusList = Collections.emptyList();
@ -67,6 +71,8 @@ public class SearchResultsAdapter extends RecyclerView.Adapter {
this.mediaPreviewsEnabled = mediaPreviewsEnabled; this.mediaPreviewsEnabled = mediaPreviewsEnabled;
this.alwaysShowSensitiveMedia = alwaysShowSensitiveMedia; this.alwaysShowSensitiveMedia = alwaysShowSensitiveMedia;
this.useAbsoluteTime = useAbsoluteTime; this.useAbsoluteTime = useAbsoluteTime;
this.showBotOverlay = showBotOverlay;
this.animateAvatar = animateAvatar;
this.linkListener = linkListener; this.linkListener = linkListener;
this.statusListener = statusListener; this.statusListener = statusListener;
@ -106,7 +112,8 @@ public class SearchResultsAdapter extends RecyclerView.Adapter {
} else { } else {
StatusViewHolder holder = (StatusViewHolder) viewHolder; StatusViewHolder holder = (StatusViewHolder) viewHolder;
int index = position - accountList.size(); int index = position - accountList.size();
holder.setupWithStatus(concreteStatusList.get(index), statusListener, mediaPreviewsEnabled); holder.setupWithStatus(concreteStatusList.get(index), statusListener,
mediaPreviewsEnabled, showBotOverlay, animateAvatar);
} }
} else { } else {
AccountViewHolder holder = (AccountViewHolder) viewHolder; AccountViewHolder holder = (AccountViewHolder) viewHolder;
@ -133,11 +140,11 @@ public class SearchResultsAdapter extends RecyclerView.Adapter {
} }
} }
public @Nullable Status getStatusAtPosition(int position) { @Nullable public Status getStatusAtPosition(int position) {
return statusList.get(position - accountList.size()); return statusList.get(position - accountList.size());
} }
public @Nullable StatusViewData.Concrete getConcreteStatusAtPosition(int position) { @Nullable public StatusViewData.Concrete getConcreteStatusAtPosition(int position) {
return concreteStatusList.get(position - accountList.size()); return concreteStatusList.get(position - accountList.size());
} }

View File

@ -2,7 +2,6 @@ package com.keylesspalace.tusky.adapter;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.preference.PreferenceManager;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
@ -16,6 +15,13 @@ import android.widget.RadioGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.ToggleButton; import android.widget.ToggleButton;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.emoji.text.EmojiCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Attachment; import com.keylesspalace.tusky.entity.Attachment;
@ -29,6 +35,7 @@ import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.DateUtils; import com.keylesspalace.tusky.util.DateUtils;
import com.keylesspalace.tusky.util.HtmlUtils; import com.keylesspalace.tusky.util.HtmlUtils;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
import com.keylesspalace.tusky.util.LinkHelper; import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.util.ThemeUtils; import com.keylesspalace.tusky.util.ThemeUtils;
import com.keylesspalace.tusky.view.MediaPreviewImageView; import com.keylesspalace.tusky.view.MediaPreviewImageView;
@ -43,12 +50,6 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.emoji.text.EmojiCompat;
import androidx.recyclerview.widget.RecyclerView;
import at.connyduck.sparkbutton.SparkButton; import at.connyduck.sparkbutton.SparkButton;
import at.connyduck.sparkbutton.SparkEventListener; import at.connyduck.sparkbutton.SparkEventListener;
import kotlin.collections.CollectionsKt; import kotlin.collections.CollectionsKt;
@ -89,11 +90,15 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
private boolean useAbsoluteTime; private boolean useAbsoluteTime;
private SimpleDateFormat shortSdf; private SimpleDateFormat shortSdf;
private SimpleDateFormat longSdf; private SimpleDateFormat longSdf;
private boolean showBotOverlay;
private final NumberFormat numberFormat = NumberFormat.getNumberInstance(); private final NumberFormat numberFormat = NumberFormat.getNumberInstance();
protected StatusBaseViewHolder(View itemView, boolean useAbsoluteTime) { private int avatarRadius48dp;
private int avatarRadius36dp;
private int avatarRadius24dp;
protected StatusBaseViewHolder(View itemView,
boolean useAbsoluteTime) {
super(itemView); super(itemView);
displayName = itemView.findViewById(R.id.status_display_name); displayName = itemView.findViewById(R.id.status_display_name);
username = itemView.findViewById(R.id.status_username); username = itemView.findViewById(R.id.status_username);
@ -104,8 +109,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
reblogButton = itemView.findViewById(R.id.status_inset); reblogButton = itemView.findViewById(R.id.status_inset);
favouriteButton = itemView.findViewById(R.id.status_favourite); favouriteButton = itemView.findViewById(R.id.status_favourite);
moreButton = itemView.findViewById(R.id.status_more); moreButton = itemView.findViewById(R.id.status_more);
reblogged = false;
favourited = false;
mediaPreviews = new MediaPreviewImageView[]{ mediaPreviews = new MediaPreviewImageView[]{
itemView.findViewById(R.id.status_media_preview_0), itemView.findViewById(R.id.status_media_preview_0),
itemView.findViewById(R.id.status_media_preview_1), itemView.findViewById(R.id.status_media_preview_1),
@ -153,7 +157,10 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
this.useAbsoluteTime = useAbsoluteTime; this.useAbsoluteTime = useAbsoluteTime;
shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault()); longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault());
showBotOverlay = PreferenceManager.getDefaultSharedPreferences(itemView.getContext()).getBoolean("showBotOverlay", true);
this.avatarRadius48dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_48dp);
this.avatarRadius36dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_36dp);
this.avatarRadius24dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_24dp);
} }
protected abstract int getMediaPreviewHeight(Context context); protected abstract int getMediaPreviewHeight(Context context);
@ -219,8 +226,13 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
} }
} }
private void setAvatar(String url, @Nullable String rebloggedUrl, boolean isBot) { private void setAvatar(String url,
@Nullable String rebloggedUrl,
boolean isBot,
boolean showBotOverlay,
boolean animateAvatar) {
int avatarRadius;
if(TextUtils.isEmpty(rebloggedUrl)) { if(TextUtils.isEmpty(rebloggedUrl)) {
avatar.setPaddingRelative(0, 0, 0, 0); avatar.setPaddingRelative(0, 0, 0, 0);
@ -235,28 +247,20 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
avatarInset.setVisibility(View.GONE); avatarInset.setVisibility(View.GONE);
} }
avatarRadius = avatarRadius48dp;
} else { } else {
int padding = Utils.convertDpToPx(avatar.getContext(), 12); int padding = Utils.convertDpToPx(avatar.getContext(), 12);
avatar.setPaddingRelative(0, 0, padding, padding); avatar.setPaddingRelative(0, 0, padding, padding);
avatarInset.setVisibility(View.VISIBLE); avatarInset.setVisibility(View.VISIBLE);
avatarInset.setBackground(null); avatarInset.setBackground(null);
Glide.with(avatarInset) ImageLoadingHelper.loadAvatar(rebloggedUrl, avatarInset, avatarRadius24dp, animateAvatar);
.asBitmap()
.load(rebloggedUrl) avatarRadius = avatarRadius36dp;
.placeholder(R.drawable.avatar_default)
.into(avatarInset);
} }
if (TextUtils.isEmpty(url)) { ImageLoadingHelper.loadAvatar(url, avatar, avatarRadius, animateAvatar);
avatar.setImageResource(R.drawable.avatar_default);
} else {
Glide.with(avatar)
.asBitmap()
.load(url)
.placeholder(R.drawable.avatar_default)
.into(avatar);
}
} }
@ -610,18 +614,22 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
} }
protected void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener, protected void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener,
boolean mediaPreviewEnabled) { boolean mediaPreviewEnabled, boolean showBotOverlay, boolean animateAvatar) {
this.setupWithStatus(status, listener, mediaPreviewEnabled, null); this.setupWithStatus(status, listener, mediaPreviewEnabled, showBotOverlay, animateAvatar, null);
} }
protected void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener, protected void setupWithStatus(StatusViewData.Concrete status,
boolean mediaPreviewEnabled, @Nullable Object payloads) { final StatusActionListener listener,
boolean mediaPreviewEnabled,
boolean showBotOverlay,
boolean animateAvatar,
@Nullable Object payloads) {
if (payloads == null) { if (payloads == null) {
setDisplayName(status.getUserFullName(), status.getAccountEmojis()); setDisplayName(status.getUserFullName(), status.getAccountEmojis());
setUsername(status.getNickname()); setUsername(status.getNickname());
setCreatedAt(status.getCreatedAt()); setCreatedAt(status.getCreatedAt());
setIsReply(status.getInReplyToId() != null); setIsReply(status.getInReplyToId() != null);
setAvatar(status.getAvatar(), status.getRebloggedAvatar(), status.isBot()); setAvatar(status.getAvatar(), status.getRebloggedAvatar(), status.isBot(), showBotOverlay, animateAvatar);
setReblogged(status.isReblogged()); setReblogged(status.isReblogged());
setFavourited(status.isFavourited()); setFavourited(status.isFavourited());
List<Attachment> attachments = status.getAttachments(); List<Attachment> attachments = status.getAttachments();

View File

@ -125,8 +125,9 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
@Override @Override
protected void setupWithStatus(final StatusViewData.Concrete status, final StatusActionListener listener, protected void setupWithStatus(final StatusViewData.Concrete status, final StatusActionListener listener,
boolean mediaPreviewEnabled, @Nullable Object payloads) { boolean mediaPreviewEnabled, boolean showBotOverlay, boolean animateAvatar,
super.setupWithStatus(status, listener, mediaPreviewEnabled, payloads); @Nullable Object payloads) {
super.setupWithStatus(status, listener, mediaPreviewEnabled, showBotOverlay, animateAvatar, payloads);
if (payloads == null) { if (payloads == null) {
setReblogAndFavCount(status.getReblogsCount(), status.getFavouritesCount(), listener); setReblogAndFavCount(status.getReblogsCount(), status.getFavouritesCount(), listener);

View File

@ -51,14 +51,15 @@ public class StatusViewHolder extends StatusBaseViewHolder {
@Override @Override
protected void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener, protected void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener,
boolean mediaPreviewEnabled, @Nullable Object payloads) { boolean mediaPreviewEnabled, boolean showBotOverlay, boolean animateAvatar,
@Nullable Object payloads) {
if (status == null || payloads == null) { if (status == null || payloads == null) {
if (status == null) { if (status == null) {
showContent(false); showContent(false);
} else { } else {
showContent(true); showContent(true);
setupCollapsedState(status, listener); setupCollapsedState(status, listener);
super.setupWithStatus(status, listener, mediaPreviewEnabled, null); super.setupWithStatus(status, listener, mediaPreviewEnabled, showBotOverlay, animateAvatar, null);
String rebloggedByDisplayName = status.getRebloggedByUsername(); String rebloggedByDisplayName = status.getRebloggedByUsername();
if (rebloggedByDisplayName == null) { if (rebloggedByDisplayName == null) {
@ -70,7 +71,7 @@ public class StatusViewHolder extends StatusBaseViewHolder {
} }
} else { } else {
super.setupWithStatus(status, listener, mediaPreviewEnabled, payloads); super.setupWithStatus(status, listener, mediaPreviewEnabled, showBotOverlay, animateAvatar, payloads);
} }
} }

View File

@ -37,6 +37,8 @@ public class ThreadAdapter extends RecyclerView.Adapter {
private StatusActionListener statusActionListener; private StatusActionListener statusActionListener;
private boolean mediaPreviewEnabled; private boolean mediaPreviewEnabled;
private boolean useAbsoluteTime; private boolean useAbsoluteTime;
private boolean showBotOverlay;
private boolean animateAvatar;
private int detailedStatusPosition; private int detailedStatusPosition;
public ThreadAdapter(StatusActionListener listener) { public ThreadAdapter(StatusActionListener listener) {
@ -44,6 +46,8 @@ public class ThreadAdapter extends RecyclerView.Adapter {
this.statuses = new ArrayList<>(); this.statuses = new ArrayList<>();
mediaPreviewEnabled = true; mediaPreviewEnabled = true;
useAbsoluteTime = false; useAbsoluteTime = false;
showBotOverlay = true;
animateAvatar = false;
detailedStatusPosition = RecyclerView.NO_POSITION; detailedStatusPosition = RecyclerView.NO_POSITION;
} }
@ -70,10 +74,10 @@ public class ThreadAdapter extends RecyclerView.Adapter {
StatusViewData.Concrete status = statuses.get(position); StatusViewData.Concrete status = statuses.get(position);
if (position == detailedStatusPosition) { if (position == detailedStatusPosition) {
StatusDetailedViewHolder holder = (StatusDetailedViewHolder) viewHolder; StatusDetailedViewHolder holder = (StatusDetailedViewHolder) viewHolder;
holder.setupWithStatus(status, statusActionListener, mediaPreviewEnabled); holder.setupWithStatus(status, statusActionListener, mediaPreviewEnabled, showBotOverlay, animateAvatar);
} else { } else {
StatusViewHolder holder = (StatusViewHolder) viewHolder; StatusViewHolder holder = (StatusViewHolder) viewHolder;
holder.setupWithStatus(status, statusActionListener, mediaPreviewEnabled); holder.setupWithStatus(status, statusActionListener, mediaPreviewEnabled, showBotOverlay, animateAvatar);
} }
} }
@ -155,6 +159,14 @@ public class ThreadAdapter extends RecyclerView.Adapter {
this.useAbsoluteTime = useAbsoluteTime; this.useAbsoluteTime = useAbsoluteTime;
} }
public void setShowBotOverlay(boolean showBotOverlay) {
this.showBotOverlay = showBotOverlay;
}
public void setAnimateAvatar(boolean animateAvatar) {
this.animateAvatar = animateAvatar;
}
public void setDetailedStatusPosition(int position) { public void setDetailedStatusPosition(int position) {
if (position != detailedStatusPosition if (position != detailedStatusPosition
&& detailedStatusPosition != RecyclerView.NO_POSITION) { && detailedStatusPosition != RecyclerView.NO_POSITION) {

View File

@ -43,14 +43,17 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
private final StatusActionListener statusListener; private final StatusActionListener statusListener;
private boolean mediaPreviewEnabled; private boolean mediaPreviewEnabled;
private boolean useAbsoluteTime; private boolean useAbsoluteTime;
private boolean showBotOverlay;
private boolean animateAvatar;
public TimelineAdapter(AdapterDataSource<StatusViewData> dataSource, public TimelineAdapter(AdapterDataSource<StatusViewData> dataSource,
StatusActionListener statusListener) { StatusActionListener statusListener) {
super();
this.dataSource = dataSource; this.dataSource = dataSource;
this.statusListener = statusListener; this.statusListener = statusListener;
mediaPreviewEnabled = true; mediaPreviewEnabled = true;
useAbsoluteTime = false; useAbsoluteTime = false;
showBotOverlay = true;
animateAvatar = false;
} }
@NonNull @NonNull
@ -89,7 +92,12 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
holder.setup(statusListener, ((StatusViewData.Placeholder) status).isLoading()); holder.setup(statusListener, ((StatusViewData.Placeholder) status).isLoading());
} else if (status instanceof StatusViewData.Concrete) { } else if (status instanceof StatusViewData.Concrete) {
StatusViewHolder holder = (StatusViewHolder) viewHolder; StatusViewHolder holder = (StatusViewHolder) viewHolder;
holder.setupWithStatus((StatusViewData.Concrete)status,statusListener, mediaPreviewEnabled,payloads!=null&&!payloads.isEmpty()?payloads.get(0):null); holder.setupWithStatus((StatusViewData.Concrete) status,
statusListener,
mediaPreviewEnabled,
showBotOverlay,
animateAvatar,
payloads != null && !payloads.isEmpty() ? payloads.get(0) : null);
} }
} }
@Override @Override
@ -111,13 +119,21 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
} }
public void setUseAbsoluteTime(boolean useAbsoluteTime){ public void setUseAbsoluteTime(boolean useAbsoluteTime){
this.useAbsoluteTime=useAbsoluteTime; this.useAbsoluteTime = useAbsoluteTime;
} }
public boolean getMediaPreviewEnabled() { public boolean getMediaPreviewEnabled() {
return mediaPreviewEnabled; return mediaPreviewEnabled;
} }
public void setShowBotOverlay(boolean showBotOverlay) {
this.showBotOverlay = showBotOverlay;
}
public void setAnimateAvatar(boolean animateAvatar) {
this.animateAvatar = animateAvatar;
}
@Override @Override
public long getItemId(int position) { public long getItemId(int position) {
return dataSource.getItemAt(position).getViewDataId(); return dataSource.getItemAt(position).getViewDataId();

View File

@ -230,6 +230,10 @@ public class NotificationsFragment extends SFragment implements
adapter.setMediaPreviewEnabled(mediaPreviewEnabled); adapter.setMediaPreviewEnabled(mediaPreviewEnabled);
boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false); boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false);
adapter.setUseAbsoluteTime(useAbsoluteTime); adapter.setUseAbsoluteTime(useAbsoluteTime);
boolean showBotOverlay = preferences.getBoolean("showBotOverlay", true);
adapter.setShowBotOverlay(showBotOverlay);
boolean animateAvatar = preferences.getBoolean("animateGifAvatars", false);
adapter.setAnimateAvatar(animateAvatar);
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
topLoading = false; topLoading = false;
@ -734,7 +738,7 @@ public class NotificationsFragment extends SFragment implements
Log.w(TAG, "Didn't find a notification for ID: " + notificationId); Log.w(TAG, "Didn't find a notification for ID: " + notificationId);
} }
public void onPreferenceChanged(String key) { private void onPreferenceChanged(String key) {
switch (key) { switch (key) {
case "fabHide": { case "fabHide": {
hideFab = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("fabHide", false); hideFab = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("fabHide", false);
@ -746,7 +750,6 @@ public class NotificationsFragment extends SFragment implements
adapter.setMediaPreviewEnabled(enabled); adapter.setMediaPreviewEnabled(enabled);
fullyRefresh(); fullyRefresh();
} }
break;
} }
} }
} }

View File

@ -46,8 +46,6 @@ class SearchFragment : SFragment(), StatusActionListener {
private lateinit var searchAdapter: SearchResultsAdapter private lateinit var searchAdapter: SearchResultsAdapter
private var alwaysShowSensitiveMedia = false private var alwaysShowSensitiveMedia = false
private var mediaPreviewEnabled = true
private var useAbsoluteTime = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_search, container, false) return inflater.inflate(R.layout.fragment_search, container, false)
@ -55,20 +53,25 @@ class SearchFragment : SFragment(), StatusActionListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val preferences = PreferenceManager.getDefaultSharedPreferences(view.context) val preferences = PreferenceManager.getDefaultSharedPreferences(view.context)
useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false) val useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false)
val showBotOverlay = preferences.getBoolean("showBotOverlay", true)
val animateAvatar = preferences.getBoolean("animateGifAvatars", false)
val account = accountManager.activeAccount val account = accountManager.activeAccount
alwaysShowSensitiveMedia = account?.alwaysShowSensitiveMedia ?: false alwaysShowSensitiveMedia = account?.alwaysShowSensitiveMedia ?: false
mediaPreviewEnabled = account?.mediaPreviewEnabled ?: true val mediaPreviewEnabled = account?.mediaPreviewEnabled ?: true
searchRecyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL)) searchRecyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
searchRecyclerView.layoutManager = LinearLayoutManager(view.context) searchRecyclerView.layoutManager = LinearLayoutManager(view.context)
searchAdapter = SearchResultsAdapter( searchAdapter = SearchResultsAdapter(
this,
this,
mediaPreviewEnabled, mediaPreviewEnabled,
alwaysShowSensitiveMedia, alwaysShowSensitiveMedia,
this, useAbsoluteTime,
this, showBotOverlay,
useAbsoluteTime) animateAvatar
)
searchRecyclerView.adapter = searchAdapter searchRecyclerView.adapter = searchAdapter
} }

View File

@ -354,6 +354,10 @@ public class TimelineFragment extends SFragment implements
adapter.setMediaPreviewEnabled(mediaPreviewEnabled); adapter.setMediaPreviewEnabled(mediaPreviewEnabled);
boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false); boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false);
adapter.setUseAbsoluteTime(useAbsoluteTime); adapter.setUseAbsoluteTime(useAbsoluteTime);
boolean showBotOverlay = preferences.getBoolean("showBotOverlay", true);
adapter.setShowBotOverlay(showBotOverlay);
boolean animateAvatar = preferences.getBoolean("animateGifAvatars", false);
adapter.setAnimateAvatar(animateAvatar);
boolean filter = preferences.getBoolean("tabFilterHomeReplies", true); boolean filter = preferences.getBoolean("tabFilterHomeReplies", true);
filterRemoveReplies = kind == Kind.HOME && !filter; filterRemoveReplies = kind == Kind.HOME && !filter;

View File

@ -0,0 +1,43 @@
@file:JvmName("ImageLoadingHelper")
package com.keylesspalace.tusky.util
import android.widget.ImageView
import androidx.annotation.Px
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.FitCenter
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.keylesspalace.tusky.R
private val fitCenterTransformation = FitCenter()
fun loadAvatar(url: String?, imageView: ImageView, @Px radius: Int, animate: Boolean) {
if(url.isNullOrBlank()) {
Glide.with(imageView)
.load(R.drawable.avatar_default)
.into(imageView)
} else {
if (animate) {
Glide.with(imageView)
.load(url)
.transform(
fitCenterTransformation,
RoundedCorners(radius)
)
.into(imageView)
} else {
Glide.with(imageView)
.asBitmap()
.load(url)
.transform(
fitCenterTransformation,
RoundedCorners(radius)
)
.into(imageView)
}
}
}

View File

@ -1,325 +0,0 @@
package com.keylesspalace.tusky.view;
/*
* Original CircleImageView Copyright 2014 - 2018 Henning Dodenhof
* Adapted to RoundedImageView by charlag in 2018
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import androidx.annotation.DrawableRes;
import androidx.appcompat.widget.AppCompatImageView;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
public class RoundedImageView extends AppCompatImageView {
private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
private static final int COLORDRAWABLE_DIMENSION = 2;
private static final int DEFAULT_BORDER_WIDTH = 0;
private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
private static final int DEFAULT_CIRCLE_BACKGROUND_COLOR = Color.TRANSPARENT;
private static float ROUNDED_PERCENT = 25;
private final RectF mDrawableRect = new RectF();
private final RectF mBorderRect = new RectF();
private final Matrix mShaderMatrix = new Matrix();
private final Paint mBitmapPaint = new Paint();
private final Paint mBorderPaint = new Paint();
private final Paint mCircleBackgroundPaint = new Paint();
private int mCircleBackgroundColor = DEFAULT_CIRCLE_BACKGROUND_COLOR;
private Bitmap mBitmap;
private BitmapShader mBitmapShader;
private int mBitmapWidth;
private int mBitmapHeight;
private float mDrawableRadius;
private float mBorderRadius;
private ColorFilter mColorFilter;
private boolean mReady;
private boolean mSetupPending;
public RoundedImageView(Context context) {
super(context);
init();
}
public RoundedImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundedImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
super.setScaleType(SCALE_TYPE);
mReady = true;
setOutlineProvider(new OutlineProvider());
if (mSetupPending) {
setup();
mSetupPending = false;
}
}
@Override
public ScaleType getScaleType() {
return SCALE_TYPE;
}
@Override
public void setScaleType(ScaleType scaleType) {
if (scaleType != SCALE_TYPE) {
throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
}
}
@Override
public void setAdjustViewBounds(boolean adjustViewBounds) {
if (adjustViewBounds) {
throw new IllegalArgumentException("adjustViewBounds not supported.");
}
}
@Override
protected void onDraw(Canvas canvas) {
if (mBitmap == null) {
return;
}
if (mCircleBackgroundColor != Color.TRANSPARENT) {
canvas.drawRoundRect(mDrawableRect, mDrawableRadius, mDrawableRadius,
mCircleBackgroundPaint);
}
canvas.drawRoundRect(mDrawableRect, mDrawableRadius, mDrawableRadius, mBitmapPaint);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setup();
}
@Override
public void setPadding(int left, int top, int right, int bottom) {
super.setPadding(left, top, right, bottom);
setup();
}
@Override
public void setPaddingRelative(int start, int top, int end, int bottom) {
super.setPaddingRelative(start, top, end, bottom);
setup();
}
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
initializeBitmap();
}
@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
initializeBitmap();
}
@Override
public void setImageResource(@DrawableRes int resId) {
super.setImageResource(resId);
initializeBitmap();
}
@Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
initializeBitmap();
}
@Override
public void setColorFilter(ColorFilter cf) {
if (cf == mColorFilter) {
return;
}
mColorFilter = cf;
applyColorFilter();
invalidate();
}
@Override
public ColorFilter getColorFilter() {
return mColorFilter;
}
private void applyColorFilter() {
mBitmapPaint.setColorFilter(mColorFilter);
}
private static Bitmap getBitmapFromDrawable(Drawable drawable) {
if (drawable == null) {
return null;
}
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
try {
Bitmap bitmap;
if (drawable instanceof ColorDrawable) {
bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
}
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private void initializeBitmap() {
mBitmap = getBitmapFromDrawable(getDrawable());
setup();
}
private void setup() {
if (!mReady) {
mSetupPending = true;
return;
}
if (getWidth() == 0 && getHeight() == 0) {
return;
}
if (mBitmap == null) {
invalidate();
return;
}
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mBitmapPaint.setAntiAlias(true);
mBitmapPaint.setShader(mBitmapShader);
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setAntiAlias(true);
mBorderPaint.setColor(DEFAULT_BORDER_COLOR);
mBorderPaint.setStrokeWidth(DEFAULT_BORDER_WIDTH);
mCircleBackgroundPaint.setStyle(Paint.Style.FILL);
mCircleBackgroundPaint.setAntiAlias(true);
mCircleBackgroundPaint.setColor(mCircleBackgroundColor);
mBitmapHeight = mBitmap.getHeight();
mBitmapWidth = mBitmap.getWidth();
mBorderRect.set(calculateBounds());
float shorterSideBorder = Math.min(mBorderRect.width(), mBorderRect.height());
mBorderRadius = shorterSideBorder / 2 * ROUNDED_PERCENT / 100;
mDrawableRect.set(mBorderRect);
float shorterSide = Math.min(mDrawableRect.width(), mDrawableRect.height());
mDrawableRadius = shorterSide / 2 * ROUNDED_PERCENT / 100;
applyColorFilter();
updateShaderMatrix();
invalidate();
}
private RectF calculateBounds() {
int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom();
int sideLength = Math.min(availableWidth, availableHeight);
float left = getPaddingLeft() + (availableWidth - sideLength) / 2f;
float top = getPaddingTop() + (availableHeight - sideLength) / 2f;
return new RectF(left, top, left + sideLength, top + sideLength);
}
private void updateShaderMatrix() {
float scale;
float dx = 0;
float dy = 0;
mShaderMatrix.set(null);
if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
scale = mDrawableRect.height() / (float) mBitmapHeight;
dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
} else {
scale = mDrawableRect.width() / (float) mBitmapWidth;
dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
}
mShaderMatrix.setScale(scale, scale);
mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
mBitmapShader.setLocalMatrix(mShaderMatrix);
}
private class OutlineProvider extends ViewOutlineProvider {
@Override
public void getOutline(View view, Outline outline) {
Rect bounds = new Rect();
mBorderRect.roundOut(bounds);
outline.setRoundRect(bounds, mBorderRadius);
}
}
}

View File

@ -352,7 +352,7 @@
<include layout="@layout/item_status_bottom_sheet" /> <include layout="@layout/item_status_bottom_sheet" />
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/accountAvatarImageView" android:id="@+id/accountAvatarImageView"
android:layout_width="@dimen/account_activity_avatar_size" android:layout_width="@dimen/account_activity_avatar_size"
android:layout_height="@dimen/account_activity_avatar_size" android:layout_height="@dimen/account_activity_avatar_size"

View File

@ -13,7 +13,7 @@
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:background="@android:color/transparent"> android:background="@android:color/transparent">
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/composeAvatar" android:id="@+id/composeAvatar"
android:layout_width="?attr/actionBarSize" android:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"

View File

@ -46,7 +46,7 @@
app:layout_constraintStart_toStartOf="@id/headerPreview" app:layout_constraintStart_toStartOf="@id/headerPreview"
app:layout_constraintTop_toTopOf="@id/headerPreview" /> app:layout_constraintTop_toTopOf="@id/headerPreview" />
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/avatarPreview" android:id="@+id/avatarPreview"
android:layout_width="80dp" android:layout_width="80dp"
android:layout_height="80dp" android:layout_height="80dp"

View File

@ -8,7 +8,7 @@
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingEnd="16dp"> android:paddingEnd="16dp">
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/account_avatar" android:id="@+id/account_avatar"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
@ -19,7 +19,7 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/avatar_default" /> tools:src="@drawable/avatar_default" />
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/account_avatar_inset" android:id="@+id/account_avatar_inset"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"

View File

@ -6,7 +6,7 @@
android:gravity="center_vertical" android:gravity="center_vertical"
android:padding="8dp"> android:padding="8dp">
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/avatar" android:id="@+id/avatar"
android:layout_width="42dp" android:layout_width="42dp"
android:layout_height="42dp" android:layout_height="42dp"

View File

@ -8,7 +8,7 @@
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp"> android:paddingRight="16dp">
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/blocked_user_avatar" android:id="@+id/blocked_user_avatar"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"

View File

@ -29,7 +29,7 @@
tools:text="ConnyDuck boosted" tools:text="ConnyDuck boosted"
tools:visibility="visible" /> tools:visibility="visible" />
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/status_avatar_2" android:id="@+id/status_avatar_2"
android:layout_width="52dp" android:layout_width="52dp"
android:layout_height="52dp" android:layout_height="52dp"
@ -42,7 +42,7 @@
app:layout_constraintTop_toTopOf="@id/status_avatar_1" app:layout_constraintTop_toTopOf="@id/status_avatar_1"
tools:src="@drawable/avatar_default" /> tools:src="@drawable/avatar_default" />
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/status_avatar_1" android:id="@+id/status_avatar_1"
android:layout_width="52dp" android:layout_width="52dp"
android:layout_height="52dp" android:layout_height="52dp"
@ -55,7 +55,7 @@
app:layout_constraintTop_toTopOf="@id/status_avatar" app:layout_constraintTop_toTopOf="@id/status_avatar"
tools:src="@drawable/avatar_default" /> tools:src="@drawable/avatar_default" />
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/status_avatar" android:id="@+id/status_avatar"
android:layout_width="52dp" android:layout_width="52dp"
android:layout_height="52dp" android:layout_height="52dp"
@ -68,7 +68,7 @@
app:layout_constraintTop_toBottomOf="@id/conversation_name" app:layout_constraintTop_toBottomOf="@id/conversation_name"
tools:src="@drawable/avatar_default" /> tools:src="@drawable/avatar_default" />
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/status_avatar_inset" android:id="@+id/status_avatar_inset"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?><!--
* This is the for folnotificationsEnabledions, the layout for the follows/following listings on account * This is the for follow notifications, the layout for the follows/following listings on account
* pages are instead in item_account.xml. * pages are instead in item_account.xml.
--> -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
@ -27,7 +27,7 @@
android:textSize="?attr/status_text_medium" android:textSize="?attr/status_text_medium"
tools:text="Someone followed you" /> tools:text="Someone followed you" />
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/notification_avatar" android:id="@+id/notification_avatar"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"

View File

@ -8,7 +8,7 @@
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp"> android:paddingRight="16dp">
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/avatar" android:id="@+id/avatar"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"

View File

@ -8,7 +8,7 @@
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp"> android:paddingRight="16dp">
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/muted_user_avatar" android:id="@+id/muted_user_avatar"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"

View File

@ -31,7 +31,7 @@
tools:text="ConnyDuck boosted" tools:text="ConnyDuck boosted"
tools:visibility="visible" /> tools:visibility="visible" />
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/status_avatar" android:id="@+id/status_avatar"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
@ -43,7 +43,7 @@
app:layout_constraintTop_toBottomOf="@id/status_info" app:layout_constraintTop_toBottomOf="@id/status_info"
tools:src="@drawable/avatar_default" /> tools:src="@drawable/avatar_default" />
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/status_avatar_inset" android:id="@+id/status_avatar_inset"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"

View File

@ -11,7 +11,7 @@
android:paddingLeft="14dp" android:paddingLeft="14dp"
android:paddingRight="14dp"> android:paddingRight="14dp">
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/status_avatar" android:id="@+id/status_avatar"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
@ -24,7 +24,7 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/avatar_default" /> tools:src="@drawable/avatar_default" />
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/status_avatar_inset" android:id="@+id/status_avatar_inset"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"

View File

@ -137,7 +137,7 @@
android:textSize="?attr/status_text_medium" android:textSize="?attr/status_text_medium"
android:visibility="gone" /> android:visibility="gone" />
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/notification_status_avatar" android:id="@+id/notification_status_avatar"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
@ -153,7 +153,7 @@
tools:ignore="RtlHardcoded,RtlSymmetry" tools:ignore="RtlHardcoded,RtlSymmetry"
tools:src="@drawable/avatar_default" /> tools:src="@drawable/avatar_default" />
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/notification_notification_avatar" android:id="@+id/notification_notification_avatar"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"

View File

@ -17,7 +17,7 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="Account has moved" /> tools:text="Account has moved" />
<com.keylesspalace.tusky.view.RoundedImageView <ImageView
android:id="@+id/accountMovedAvatar" android:id="@+id/accountMovedAvatar"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"

View File

@ -32,4 +32,12 @@
<dimen name="preference_icon_size">20dp</dimen> <dimen name="preference_icon_size">20dp</dimen>
<dimen name="selected_drag_item_elevation">12dp</dimen> <dimen name="selected_drag_item_elevation">12dp</dimen>
<dimen name="avatar_radius_94dp">11.75dp</dimen> <!-- 1/8 of 100dp - 2 * 3dp padding -->
<dimen name="avatar_radius_80dp">10dp</dimen> <!-- 1/8 of 80dp -->
<dimen name="avatar_radius_48dp">6dp</dimen> <!-- 1/8 of 48dp -->
<dimen name="avatar_radius_42dp">5.25dp</dimen> <!-- 1/8 of 42dp -->
<dimen name="avatar_radius_40dp">5dp</dimen> <!-- 1/8 of 40dp -->
<dimen name="avatar_radius_36dp">4.5dp</dimen> <!-- 1/8 of 36dp -->
<dimen name="avatar_radius_24dp">3dp</dimen> <!-- 1/8 of 24dp -->
</resources> </resources>

View File

@ -216,6 +216,9 @@
<string name="pref_title_custom_tabs">Use Chrome Custom Tabs</string> <string name="pref_title_custom_tabs">Use Chrome Custom Tabs</string>
<string name="pref_title_hide_follow_button">Hide compose button while scrolling</string> <string name="pref_title_hide_follow_button">Hide compose button while scrolling</string>
<string name="pref_title_language">Language</string> <string name="pref_title_language">Language</string>
<string name="pref_title_bot_overlay">Show indicator for bots</string>
<string name="pref_title_animate_gif_avatars">Animate GIF avatars</string>
<string name="pref_title_status_filter">Timeline filtering</string> <string name="pref_title_status_filter">Timeline filtering</string>
<string name="pref_title_status_tabs">Tabs</string> <string name="pref_title_status_tabs">Tabs</string>
<string name="pref_title_show_boosts">Show boosts</string> <string name="pref_title_show_boosts">Show boosts</string>
@ -463,7 +466,6 @@
<string name="compose_shortcut_long_label">Compose Toot</string> <string name="compose_shortcut_long_label">Compose Toot</string>
<string name="compose_shortcut_short_label">Compose</string> <string name="compose_shortcut_short_label">Compose</string>
<string name="pref_title_bot_overlay">Show indicator for bots</string>
<string name="notification_clear_text">Are you sure you want to permanently clear all your notifications?</string> <string name="notification_clear_text">Are you sure you want to permanently clear all your notifications?</string>
<string name="compose_preview_image_description">Actions for image %s</string> <string name="compose_preview_image_description">Actions for image %s</string>

View File

@ -49,6 +49,11 @@
android:defaultValue="true" android:defaultValue="true"
android:key="showBotOverlay" android:key="showBotOverlay"
android:title="@string/pref_title_bot_overlay" /> android:title="@string/pref_title_bot_overlay" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="animateGifAvatars"
android:title="@string/pref_title_animate_gif_avatars" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/pref_title_browser_settings"> <PreferenceCategory android:title="@string/pref_title_browser_settings">