improved profile image on pre-lollipop devices

added sponsors in README
This commit is contained in:
Mariotaku Lee 2017-03-07 17:22:02 +08:00
parent d7e107c126
commit c52aa2da68
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
39 changed files with 324 additions and 344 deletions

View File

@ -62,11 +62,11 @@ Bitcoin: `1FHAVAzge7cj1LfCTMfnLL49DgA3mVUCuW`
**Donators** **Donators**
[@TwidereProject/donators](https://twitter.com/TwidereProject/lists/donators), if you haven't find your name, please contact @TwidereProject :) Checkout enhanced features on Google Play!
Buy me a ~~bread~~ [game](http://steamcommunity.com/id/mariotaku/wishlist) or anything you want :) **Sponsors**
[帮我支付宝账户里随便加点钱](https://twitter.com/xmxsuperstar/status/724094631621750785) <a href='http://www.sujitech.com/'><img src='resources/logos/sujitech_logo.png'/></a>
--- ---

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -40,7 +40,7 @@ import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R; import org.mariotaku.twidere.R;
import org.mariotaku.twidere.model.AccountDetails; import org.mariotaku.twidere.model.AccountDetails;
import org.mariotaku.twidere.model.util.AccountUtils; import org.mariotaku.twidere.model.util.AccountUtils;
import org.mariotaku.twidere.util.MediaLoaderWrapper; import org.mariotaku.twidere.util.MediaPreloader;
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper; import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
import javax.inject.Inject; import javax.inject.Inject;
@ -94,7 +94,7 @@ public abstract class AccountsListPreference extends TintedPreferenceCategory im
private final SharedPreferences mSwitchPreference; private final SharedPreferences mSwitchPreference;
@Inject @Inject
MediaLoaderWrapper mImageLoader; MediaPreloader mediaPreloader;
public AccountItemPreference(final Context context, final AccountDetails account, public AccountItemPreference(final Context context, final AccountDetails account,
@Nullable final String switchKey, final boolean switchDefault) { @Nullable final String switchKey, final boolean switchDefault) {
@ -122,7 +122,7 @@ public abstract class AccountsListPreference extends TintedPreferenceCategory im
super.onBindViewHolder(holder); super.onBindViewHolder(holder);
final View iconView = holder.findViewById(android.R.id.icon); final View iconView = holder.findViewById(android.R.id.icon);
if (iconView instanceof PreferenceImageView) { if (iconView instanceof PreferenceImageView) {
final PreferenceImageView imageView = (PreferenceImageView) iconView; final ImageView imageView = (ImageView) iconView;
final int maxSize = getContext().getResources().getDimensionPixelSize(R.dimen.element_size_normal); final int maxSize = getContext().getResources().getDimensionPixelSize(R.dimen.element_size_normal);
imageView.setMinimumWidth(maxSize); imageView.setMinimumWidth(maxSize);
imageView.setMinimumHeight(maxSize); imageView.setMinimumHeight(maxSize);

View File

@ -1,156 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.util.media
import android.media.MediaPlayer
import android.media.MediaPlayer.*
/**
* From https://gist.github.com/danielhawkes/1029568
*
* A wrapper class for [android.media.MediaPlayer].
*
* Encapsulates an instance of MediaPlayer, and makes a record of its internal state accessible via a
* [MediaPlayerWrapper.getState] accessor. Most of the frequently used methods are available, but some still
* need adding.
*
*/
class MediaPlayerWrapper(val player: MediaPlayer) {
var state: State = State.IDLE
private set
var onPreparedListener: OnPreparedListener? = null
var onCompletionListener: OnCompletionListener? = null
var onBufferingUpdateListener: OnBufferingUpdateListener? = null
var onErrorListener: OnErrorListener? = null
var onInfoListener: OnInfoListener? = null
init {
player.setOnPreparedListener { mp ->
state = State.PREPARED
onPreparedListener?.onPrepared(mp)
}
player.setOnCompletionListener { mp ->
state = State.PLAYBACK_COMPLETE
onCompletionListener?.onCompletion(mp)
}
player.setOnBufferingUpdateListener { mp, percent ->
onBufferingUpdateListener?.onBufferingUpdate(mp, percent)
}
player.setOnErrorListener { mp, what, extra ->
state = State.ERROR
return@setOnErrorListener onErrorListener?.onError(mp, what, extra) ?: false
}
player.setOnInfoListener { mp, what, extra ->
return@setOnInfoListener onInfoListener?.onInfo(mp, what, extra) ?: false
}
}
fun setDataSource(path: String) {
if (!state.canSetDataSource) throw illegalState()
player.setDataSource(path)
state = State.INITIALIZED
}
fun prepareAsync() {
if (!state.canPrepare) throw illegalState()
player.prepareAsync()
state = State.PREPARING
}
val isPlaying: Boolean
get() = state.canAccessInfo && player.isPlaying
fun seekTo(msec: Int) {
if (!state.canAccessInfo) throw illegalState()
player.seekTo(msec)
}
fun pause() {
if (!state.canPause) throw illegalState()
player.pause()
state = State.PAUSED
throw illegalState()
}
fun start() {
if (!state.canOperate) throw illegalState()
player.start()
state = State.STARTED
}
fun stop() {
if (!state.canOperate) throw illegalState()
player.stop()
state = State.STOPPED
throw illegalState()
}
fun reset() {
player.reset()
state = State.IDLE
}
fun release() {
player.release()
}
fun setVolume(leftVolume: Float, rightVolume: Float) {
if (!state.canOperate) throw illegalState()
player.setVolume(leftVolume, rightVolume)
}
val currentPosition: Int
get() {
if (!state.canAccessInfo) throw illegalState()
return player.currentPosition
}
val duration: Int
get() {
if (!state.canAccessInfo) throw illegalState()
return player.duration
}
private fun illegalState(): Throwable {
throw IllegalStateException("Illegal state $state")
}
/* METHOD WRAPPING FOR STATE CHANGES */
enum class State(
val canPrepare: Boolean = false,
val canOperate: Boolean = false,
val canPause: Boolean = false,
val canAccessInfo: Boolean = false,
val canSetDataSource: Boolean = false
) {
IDLE(canSetDataSource = true),
ERROR(),
INITIALIZED(canPrepare = true),
PREPARING(),
PREPARED(canAccessInfo = true, canOperate = true),
STARTED(canAccessInfo = true, canOperate = true, canPause = true),
STOPPED(canAccessInfo = true, canPrepare = true),
PLAYBACK_COMPLETE(canAccessInfo = true, canOperate = true),
PAUSED(canAccessInfo = true, canOperate = true, canPause = true)
}
}

View File

@ -163,10 +163,18 @@ public class ShapedImageView extends AppCompatImageView {
mCornerRadius = radius; mCornerRadius = radius;
} }
public float getCornerRadius() {
return mCornerRadius;
}
public void setCornerRadiusRatio(float ratio) { public void setCornerRadiusRatio(float ratio) {
mCornerRadiusRatio = ratio; mCornerRadiusRatio = ratio;
} }
public float getCornerRadiusRatio() {
return mCornerRadiusRatio;
}
public void setDrawShadow(final boolean drawShadow) { public void setDrawShadow(final boolean drawShadow) {
mDrawShadow = drawShadow; mDrawShadow = drawShadow;
} }

View File

@ -1,58 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.view.holder;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.view.iface.IColorLabelView;
public class AccountViewHolder {
public final ImageView profileImage;
public final TextView name, screenName;
public final CompoundButton toggle;
public final View toggleContainer;
public final ImageView accountType;
private final IColorLabelView content;
private final View dragHandle;
public AccountViewHolder(final View view) {
content = (IColorLabelView) view;
name = (TextView) view.findViewById(android.R.id.text1);
screenName = (TextView) view.findViewById(android.R.id.text2);
profileImage = (ImageView) view.findViewById(android.R.id.icon);
toggle = (CompoundButton) view.findViewById(android.R.id.toggle);
toggleContainer = view.findViewById(R.id.toggle_container);
dragHandle = view.findViewById(R.id.drag_handle);
accountType = (ImageView) view.findViewById(R.id.account_type);
}
public void setAccountColor(final int color) {
content.drawEnd(color);
}
public void setSortEnabled(boolean enabled) {
dragHandle.setVisibility(enabled ? View.VISIBLE : View.GONE);
}
}

View File

@ -91,8 +91,8 @@ class AccountSelectorActivity : BaseActivity(), OnItemClickListener {
setContentView(R.layout.activity_account_selector) setContentView(R.layout.activity_account_selector)
DataStoreUtils.prepareDatabase(this) DataStoreUtils.prepareDatabase(this)
adapter = AccountDetailsAdapter(this, Glide.with(this)).apply { adapter = AccountDetailsAdapter(this, Glide.with(this)).apply {
setSwitchEnabled(!isSingleSelection) switchEnabled = !isSingleSelection
setSortEnabled(false) sortEnabled = false
val am = AccountManager.get(context) val am = AccountManager.get(context)
val allAccountDetails = AccountUtils.getAllAccountDetails(am, AccountUtils.getAccounts(am), false) val allAccountDetails = AccountUtils.getAllAccountDetails(am, AccountUtils.getAccounts(am), false)
val extraKeys = onlyIncludeKeys val extraKeys = onlyIncludeKeys

View File

@ -463,7 +463,8 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
if (accounts.size == 1) { if (accounts.size == 1) {
accountsCount.setText(null) accountsCount.setText(null)
val account = accounts[0] val account = accounts[0]
Glide.with(this).loadProfileImage(this, account).into(accountProfileImage) val profileImageStyle = preferences[profileImageStyleKey]
Glide.with(this).loadProfileImage(this, account, profileImageStyle).into(accountProfileImage)
accountProfileImage.setBorderColor(account.color) accountProfileImage.setBorderColor(account.color)
} else { } else {
accountsCount.setText(accounts.size.toString()) accountsCount.setText(accounts.size.toString())
@ -1424,7 +1425,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
itemView.alpha = if (isSelected) 1f else 0.33f itemView.alpha = if (isSelected) 1f else 0.33f
(itemView as CheckableLinearLayout).isChecked = isSelected (itemView as CheckableLinearLayout).isChecked = isSelected
val context = adapter.context val context = adapter.context
adapter.requestManager.loadProfileImage(context, account).into(iconView) adapter.requestManager.loadProfileImage(context, account, adapter.profileImageStyle).into(iconView)
iconView.setBorderColor(account.color) iconView.setBorderColor(account.color)
nameView.text = if (adapter.isNameFirst) account.user.name else "@" + account.user.screen_name nameView.text = if (adapter.isNameFirst) account.user.name else "@" + account.user.screen_name
} }

View File

@ -50,6 +50,7 @@ import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY
import org.mariotaku.twidere.constant.KeyboardShortcutConstants.ACTION_NAVIGATION_BACK import org.mariotaku.twidere.constant.KeyboardShortcutConstants.ACTION_NAVIGATION_BACK
import org.mariotaku.twidere.constant.KeyboardShortcutConstants.CONTEXT_TAG_NAVIGATION import org.mariotaku.twidere.constant.KeyboardShortcutConstants.CONTEXT_TAG_NAVIGATION
import org.mariotaku.twidere.constant.newDocumentApiKey import org.mariotaku.twidere.constant.newDocumentApiKey
import org.mariotaku.twidere.constant.profileImageStyleKey
import org.mariotaku.twidere.extension.loadProfileImage import org.mariotaku.twidere.extension.loadProfileImage
import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.SuggestionItem import org.mariotaku.twidere.model.SuggestionItem
@ -264,6 +265,7 @@ class QuickSearchBarActivity : BaseActivity(), OnClickListener, LoaderCallbacks<
private val activity: QuickSearchBarActivity private val activity: QuickSearchBarActivity
) : CursorAdapter(activity, null, 0), OnClickListener { ) : CursorAdapter(activity, null, 0), OnClickListener {
private val profileImageStyle = activity.preferences[profileImageStyleKey]
private val requestManager = Glide.with(activity) private val requestManager = Glide.with(activity)
private val inflater = LayoutInflater.from(activity) private val inflater = LayoutInflater.from(activity)
private val userColorNameManager = activity.userColorNameManager private val userColorNameManager = activity.userColorNameManager
@ -320,7 +322,8 @@ class QuickSearchBarActivity : BaseActivity(), OnClickListener, LoaderCallbacks<
holder.text2.visibility = View.VISIBLE holder.text2.visibility = View.VISIBLE
holder.text2.text = "@${cursor.getString(indices.summary)}" holder.text2.text = "@${cursor.getString(indices.summary)}"
holder.icon.clearColorFilter() holder.icon.clearColorFilter()
requestManager.loadProfileImage(context, cursor.getString(indices.icon)).into(holder.icon) requestManager.loadProfileImage(context, cursor.getString(indices.icon),
profileImageStyle).into(holder.icon)
} }
VIEW_TYPE_USER_SCREEN_NAME -> { VIEW_TYPE_USER_SCREEN_NAME -> {
val holder = view.tag as UserViewHolder val holder = view.tag as UserViewHolder

View File

@ -20,15 +20,14 @@
package org.mariotaku.twidere.adapter package org.mariotaku.twidere.adapter
import android.content.Context import android.content.Context
import android.support.v7.widget.RecyclerViewAccessor
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.CompoundButton import android.widget.CompoundButton
import com.bumptech.glide.RequestManager import com.bumptech.glide.RequestManager
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.extension.loadProfileImage
import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import org.mariotaku.twidere.view.holder.AccountViewHolder import org.mariotaku.twidere.view.holder.AccountViewHolder
@ -37,11 +36,19 @@ class AccountDetailsAdapter(
requestManager: RequestManager requestManager: RequestManager
) : BaseArrayAdapter<AccountDetails>(context, R.layout.list_item_account, requestManager = requestManager) { ) : BaseArrayAdapter<AccountDetails>(context, R.layout.list_item_account, requestManager = requestManager) {
private var sortEnabled: Boolean = false var sortEnabled: Boolean = false
private var switchEnabled: Boolean = false set(value) {
field = value
notifyDataSetChanged()
}
var switchEnabled: Boolean = false
set(value) {
field = value
notifyDataSetChanged()
}
var accountToggleListener: ((Int, Boolean) -> Unit)? = null var accountToggleListener: ((Int, Boolean) -> Unit)? = null
private val checkedChangeListener = CompoundButton.OnCheckedChangeListener { buttonView, isChecked -> val checkedChangeListener = CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
val position = buttonView.tag as? Int ?: return@OnCheckedChangeListener val position = buttonView.tag as? Int ?: return@OnCheckedChangeListener
accountToggleListener?.invoke(position, isChecked) accountToggleListener?.invoke(position, isChecked)
} }
@ -53,26 +60,13 @@ class AccountDetailsAdapter(
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val view = super.getView(position, convertView, parent) val view = super.getView(position, convertView, parent)
val holder = view.tag as? AccountViewHolder ?: run { val holder = view.tag as? AccountViewHolder ?: run {
val h = AccountViewHolder(view) val h = AccountViewHolder(view, this)
view.tag = h view.tag = h
return@run h return@run h
} }
RecyclerViewAccessor.setLayoutPosition(holder, position)
val details = getItem(position) val details = getItem(position)
holder.name.text = details.user.name holder.display(details)
holder.screenName.text = String.format("@%s", details.user.screen_name)
holder.setAccountColor(details.color)
if (profileImageEnabled) {
requestManager.loadProfileImage(context, details).into(holder.profileImage)
} else {
// TODO: display stub image?
}
val accountType = details.type
holder.accountType.setImageResource(AccountUtils.getAccountTypeIcon(accountType))
holder.toggle.isChecked = details.activated
holder.toggle.setOnCheckedChangeListener(checkedChangeListener)
holder.toggle.tag = position
holder.toggleContainer.visibility = if (switchEnabled) View.VISIBLE else View.GONE
holder.setSortEnabled(sortEnabled)
return view return view
} }
@ -84,18 +78,6 @@ class AccountDetailsAdapter(
return getItem(position).key.hashCode().toLong() return getItem(position).key.hashCode().toLong()
} }
fun setSwitchEnabled(enabled: Boolean) {
if (switchEnabled == enabled) return
switchEnabled = enabled
notifyDataSetChanged()
}
fun setSortEnabled(sortEnabled: Boolean) {
if (this.sortEnabled == sortEnabled) return
this.sortEnabled = sortEnabled
notifyDataSetChanged()
}
fun drop(from: Int, to: Int) { fun drop(from: Int, to: Int) {
val fromItem = getItem(from) val fromItem = getItem(from)
removeAt(from) removeAt(from)

View File

@ -69,7 +69,7 @@ class AccountsSpinnerAdapter(
if (profileImageEnabled) { if (profileImageEnabled) {
icon.visibility = View.VISIBLE icon.visibility = View.VISIBLE
icon.style = profileImageStyle icon.style = profileImageStyle
requestManager.loadProfileImage(context, item.user).into(icon) requestManager.loadProfileImage(context, item.user, profileImageStyle).into(icon)
} else { } else {
icon.visibility = View.GONE icon.visibility = View.GONE
} }

View File

@ -35,7 +35,6 @@ import org.mariotaku.twidere.extension.loadProfileImage
import org.mariotaku.twidere.model.SuggestionItem import org.mariotaku.twidere.model.SuggestionItem
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.Suggestions import org.mariotaku.twidere.provider.TwidereDataStore.Suggestions
import org.mariotaku.twidere.util.MediaLoaderWrapper
import org.mariotaku.twidere.util.SharedPreferencesWrapper import org.mariotaku.twidere.util.SharedPreferencesWrapper
import org.mariotaku.twidere.util.UserColorNameManager import org.mariotaku.twidere.util.UserColorNameManager
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
@ -77,7 +76,7 @@ class ComposeAutoCompleteAdapter(context: Context, val requestManager: RequestMa
text2.text = String.format("@%s", cursor.getString(indices.summary)) text2.text = String.format("@%s", cursor.getString(indices.summary))
if (displayProfileImage) { if (displayProfileImage) {
val profileImageUrl = cursor.getString(indices.icon) val profileImageUrl = cursor.getString(indices.icon)
requestManager.loadProfileImage(context, profileImageUrl).into(icon) requestManager.loadProfileImage(context, profileImageUrl, profileImageStyle).into(icon)
} else { } else {
//TODO cancel image load //TODO cancel image load
} }

View File

@ -84,7 +84,7 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
@Inject @Inject
lateinit internal var extraFeaturesService: ExtraFeaturesService lateinit internal var extraFeaturesService: ExtraFeaturesService
@Inject @Inject
lateinit internal var mediaLoader: MediaLoaderWrapper lateinit internal var mediaPreloader: MediaPreloader
@Inject @Inject
lateinit internal var contentNotificationManager: ContentNotificationManager lateinit internal var contentNotificationManager: ContentNotificationManager
@ -250,7 +250,7 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
(mediaDownloader as TwidereMediaDownloader).reloadConnectivitySettings() (mediaDownloader as TwidereMediaDownloader).reloadConnectivitySettings()
} }
KEY_MEDIA_PRELOAD, KEY_PRELOAD_WIFI_ONLY -> { KEY_MEDIA_PRELOAD, KEY_PRELOAD_WIFI_ONLY -> {
mediaLoader.reloadOptions(preferences) mediaPreloader.reloadOptions(preferences)
} }
KEY_NAME_FIRST, KEY_I_WANT_MY_STARS_BACK -> { KEY_NAME_FIRST, KEY_I_WANT_MY_STARS_BACK -> {
contentNotificationManager.updatePreferences() contentNotificationManager.updatePreferences()

View File

@ -31,15 +31,17 @@ import org.mariotaku.twidere.extension.model.getBestProfileBanner
import org.mariotaku.twidere.extension.model.user import org.mariotaku.twidere.extension.model.user
import org.mariotaku.twidere.model.* import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.util.Utils import org.mariotaku.twidere.util.Utils
import org.mariotaku.twidere.util.glide.RoundedRectTransformation
import org.mariotaku.twidere.view.ShapedImageView import org.mariotaku.twidere.view.ShapedImageView
fun RequestManager.loadProfileImage( fun RequestManager.loadProfileImage(
context: Context, context: Context,
url: String?, url: String?,
@ImageShapeStyle style: Int = ImageShapeStyle.SHAPE_CIRCLE, @ImageShapeStyle style: Int,
cornerRadius: Float = 0f, cornerRadiusRatio: Float = 0f,
size: String? = null size: String? = null
): DrawableRequestBuilder<String?> { ): DrawableRequestBuilder<String?> {
return configureLoadProfileImage(context, style) { return configureLoadProfileImage(context, style, cornerRadius, cornerRadiusRatio) {
if (url == null || size == null) { if (url == null || size == null) {
return@configureLoadProfileImage load(url) return@configureLoadProfileImage load(url)
} else { } else {
@ -49,24 +51,28 @@ fun RequestManager.loadProfileImage(
} }
fun RequestManager.loadProfileImage(context: Context, resourceId: Int, fun RequestManager.loadProfileImage(context: Context, resourceId: Int,
@ImageShapeStyle shapeStyle: Int = ImageShapeStyle.SHAPE_CIRCLE): DrawableRequestBuilder<Int> { @ImageShapeStyle shapeStyle: Int,
return configureLoadProfileImage(context, shapeStyle) { load(resourceId) } cornerRadius: Float = 0f, cornerRadiusRatio: Float = 0f): DrawableRequestBuilder<Int> {
return configureLoadProfileImage(context, shapeStyle, cornerRadius, cornerRadiusRatio) { load(resourceId) }
} }
fun RequestManager.loadProfileImage(context: Context, account: AccountDetails, fun RequestManager.loadProfileImage(context: Context, account: AccountDetails,
@ImageShapeStyle shapeStyle: Int = ImageShapeStyle.SHAPE_CIRCLE, @ImageShapeStyle shapeStyle: Int,
cornerRadius: Float = 0f, cornerRadiusRatio: Float = 0f,
size: String? = null): DrawableRequestBuilder<String?> { size: String? = null): DrawableRequestBuilder<String?> {
return loadProfileImage(context, account.user, shapeStyle, size) return loadProfileImage(context, account.user, shapeStyle, cornerRadius, cornerRadiusRatio, size)
} }
fun RequestManager.loadProfileImage(context: Context, user: ParcelableUser, fun RequestManager.loadProfileImage(context: Context, user: ParcelableUser,
@ImageShapeStyle shapeStyle: Int = ImageShapeStyle.SHAPE_CIRCLE, @ImageShapeStyle shapeStyle: Int,
cornerRadius: Float = 0f, cornerRadiusRatio: Float = 0f,
size: String? = null): DrawableRequestBuilder<String?> { size: String? = null): DrawableRequestBuilder<String?> {
if (user.extras != null && user.extras.profile_image_url_fallback == null) { if (user.extras != null && user.extras.profile_image_url_fallback == null) {
// No fallback image, use compatible logic // No fallback image, use compatible logic
return loadProfileImage(context, user.profile_image_url, shapeStyle, size) return loadProfileImage(context, user.profile_image_url, shapeStyle, cornerRadius,
cornerRadiusRatio, size)
} }
return configureLoadProfileImage(context, shapeStyle) { return configureLoadProfileImage(context, shapeStyle, cornerRadius, cornerRadiusRatio) {
if (size != null) { if (size != null) {
return@configureLoadProfileImage load(Utils.getTwitterProfileImageOfSize(user.profile_image_url, size)) return@configureLoadProfileImage load(Utils.getTwitterProfileImageOfSize(user.profile_image_url, size))
} else { } else {
@ -76,55 +82,70 @@ fun RequestManager.loadProfileImage(context: Context, user: ParcelableUser,
} }
fun RequestManager.loadProfileImage(context: Context, userList: ParcelableUserList, fun RequestManager.loadProfileImage(context: Context, userList: ParcelableUserList,
@ImageShapeStyle shapeStyle: Int = ImageShapeStyle.SHAPE_CIRCLE): DrawableRequestBuilder<String?> { @ImageShapeStyle shapeStyle: Int = ImageShapeStyle.SHAPE_CIRCLE,
return configureLoadProfileImage(context, shapeStyle) { load(userList.user_profile_image_url) } cornerRadius: Float = 0f, cornerRadiusRatio: Float = 0f): DrawableRequestBuilder<String?> {
return configureLoadProfileImage(context, shapeStyle, cornerRadius, cornerRadiusRatio) {
load(userList.user_profile_image_url)
}
} }
fun RequestManager.loadProfileImage(context: Context, group: ParcelableGroup, fun RequestManager.loadProfileImage(context: Context, group: ParcelableGroup,
@ImageShapeStyle shapeStyle: Int = ImageShapeStyle.SHAPE_CIRCLE): DrawableRequestBuilder<String?> { @ImageShapeStyle shapeStyle: Int = ImageShapeStyle.SHAPE_CIRCLE,
return configureLoadProfileImage(context, shapeStyle) { load(group.homepage_logo) } cornerRadius: Float = 0f, cornerRadiusRatio: Float = 0f): DrawableRequestBuilder<String?> {
return configureLoadProfileImage(context, shapeStyle, cornerRadius, cornerRadiusRatio) {
load(group.homepage_logo)
}
} }
fun RequestManager.loadProfileImage(context: Context, status: ParcelableStatus, fun RequestManager.loadProfileImage(context: Context, status: ParcelableStatus,
@ImageShapeStyle shapeStyle: Int = ImageShapeStyle.SHAPE_CIRCLE, @ImageShapeStyle shapeStyle: Int,
cornerRadius: Float = 0f, cornerRadiusRatio: Float = 0f,
size: String? = null): DrawableRequestBuilder<String?> { size: String? = null): DrawableRequestBuilder<String?> {
if (status.extras != null && status.extras.user_profile_image_url_fallback == null) { if (status.extras != null && status.extras.user_profile_image_url_fallback == null) {
// No fallback image, use compatible logic // No fallback image, use compatible logic
return loadProfileImage(context, status.user_profile_image_url, shapeStyle, size) return loadProfileImage(context, status.user_profile_image_url, shapeStyle, cornerRadius,
cornerRadiusRatio, size)
}
return configureLoadProfileImage(context, shapeStyle, cornerRadius, cornerRadiusRatio) {
load(status.user_profile_image_url)
} }
return configureLoadProfileImage(context, shapeStyle) { load(status.user_profile_image_url) }
} }
fun RequestManager.loadProfileImage(context: Context, conversation: ParcelableMessageConversation, fun RequestManager.loadProfileImage(context: Context, conversation: ParcelableMessageConversation,
@ImageShapeStyle shapeStyle: Int = ImageShapeStyle.SHAPE_CIRCLE, @ImageShapeStyle shapeStyle: Int,
cornerRadius: Float = 0f, cornerRadiusRatio: Float = 0f,
size: String? = null): DrawableRequestBuilder<*> { size: String? = null): DrawableRequestBuilder<*> {
if (conversation.conversation_type == ParcelableMessageConversation.ConversationType.ONE_TO_ONE) { if (conversation.conversation_type == ParcelableMessageConversation.ConversationType.ONE_TO_ONE) {
val user = conversation.user val user = conversation.user
if (user != null) { if (user != null) {
return loadProfileImage(context, user, shapeStyle, size) return loadProfileImage(context, user, shapeStyle, cornerRadius, cornerRadiusRatio, size)
} else { } else {
// TODO: show default conversation icon // TODO: show default conversation icon
return loadProfileImage(context, org.mariotaku.twidere.R.drawable.ic_profile_image_default_group) return loadProfileImage(context, R.drawable.ic_profile_image_default_group, shapeStyle)
} }
} else { } else {
return loadProfileImage(context, conversation.conversation_avatar, shapeStyle, size) return loadProfileImage(context, conversation.conversation_avatar, shapeStyle, cornerRadius,
.placeholder(R.drawable.ic_profile_image_default_group) cornerRadiusRatio, size).placeholder(R.drawable.ic_profile_image_default_group)
} }
} }
fun RequestManager.loadOriginalProfileImage(context: Context, user: ParcelableUser, fun RequestManager.loadOriginalProfileImage(context: Context, user: ParcelableUser,
@ImageShapeStyle shapeStyle: Int = ImageShapeStyle.SHAPE_CIRCLE): DrawableRequestBuilder<String> { @ImageShapeStyle shapeStyle: Int, cornerRadius: Float = 0f, cornerRadiusRatio: Float = 0f
): DrawableRequestBuilder<String> {
val original = user.extras.profile_image_url_original?.takeUnless(String::isEmpty) val original = user.extras.profile_image_url_original?.takeUnless(String::isEmpty)
?: Utils.getOriginalTwitterProfileImage(user.profile_image_url) ?: Utils.getOriginalTwitterProfileImage(user.profile_image_url)
return configureLoadProfileImage(context, shapeStyle) { load(original) } return configureLoadProfileImage(context, shapeStyle, cornerRadius, cornerRadiusRatio) {
load(original)
}
} }
fun RequestManager.loadProfileBanner(context: Context, user: ParcelableUser, width: Int): DrawableTypeRequest<String?> { fun RequestManager.loadProfileBanner(context: Context, user: ParcelableUser, width: Int): DrawableTypeRequest<String?> {
return load(user.getBestProfileBanner(width)) return load(user.getBestProfileBanner(width))
} }
internal inline fun <T> configureLoadProfileImage(context: Context, shapeStyle: Int, internal inline fun <T> configureLoadProfileImage(context: Context, @ImageShapeStyle shapeStyle: Int,
create: () -> DrawableTypeRequest<T>): DrawableRequestBuilder<T> { cornerRadius: Float = 0f, cornerRadiusRatio: Float = 0f, create: () -> DrawableTypeRequest<T>
): DrawableRequestBuilder<T> {
val builder = create() val builder = create()
builder.diskCacheStrategy(DiskCacheStrategy.RESULT) builder.diskCacheStrategy(DiskCacheStrategy.RESULT)
builder.dontAnimate() builder.dontAnimate()
@ -134,6 +155,8 @@ internal inline fun <T> configureLoadProfileImage(context: Context, shapeStyle:
builder.bitmapTransform(CropCircleTransformation(context)) builder.bitmapTransform(CropCircleTransformation(context))
} }
ImageShapeStyle.SHAPE_RECTANGLE -> { ImageShapeStyle.SHAPE_RECTANGLE -> {
builder.bitmapTransform(RoundedRectTransformation(context, cornerRadius,
cornerRadiusRatio))
} }
} }
} }

View File

@ -451,7 +451,9 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
//TODO complete border color //TODO complete border color
clickedColors = clickedImageView.borderColors clickedColors = clickedImageView.borderColors
val oldSelectedAccount = accountsAdapter.selectedAccount ?: return val oldSelectedAccount = accountsAdapter.selectedAccount ?: return
Glide.with(this@AccountsDashboardFragment).loadProfileImage(context, oldSelectedAccount) val profileImageStyle = preferences[profileImageStyleKey]
Glide.with(this@AccountsDashboardFragment).loadProfileImage(context, oldSelectedAccount,
profileImageStyle, clickedImageView.cornerRadius, clickedImageView.cornerRadiusRatio)
.into(clickedImageView).onLoadStarted(profileDrawable) .into(clickedImageView).onLoadStarted(profileDrawable)
//TODO complete border color //TODO complete border color
clickedImageView.setBorderColors(*profileImageView.borderColors) clickedImageView.setBorderColors(*profileImageView.borderColors)
@ -510,8 +512,9 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
val account = accountsAdapter.selectedAccount ?: return val account = accountsAdapter.selectedAccount ?: return
accountProfileNameView.text = account.user.name accountProfileNameView.text = account.user.name
accountProfileScreenNameView.text = "@${account.user.screen_name}" accountProfileScreenNameView.text = "@${account.user.screen_name}"
Glide.with(this).loadProfileImage(context, account, size = ProfileImageSize.REASONABLY_SMALL) Glide.with(this).loadProfileImage(context, account, preferences[profileImageStyleKey],
.placeholder(profileImageSnapshot).into(accountProfileImageView) accountProfileImageView.cornerRadius, accountProfileImageView.cornerRadiusRatio,
ProfileImageSize.REASONABLY_SMALL).placeholder(profileImageSnapshot).into(accountProfileImageView)
//TODO complete border color //TODO complete border color
accountProfileImageView.setBorderColors(account.color) accountProfileImageView.setBorderColors(account.color)
accountProfileBanner.showNext() accountProfileBanner.showNext()
@ -609,6 +612,7 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
init { init {
itemView.setOnClickListener(this) itemView.setOnClickListener(this)
iconView = itemView.findViewById(android.R.id.icon) as ShapedImageView iconView = itemView.findViewById(android.R.id.icon) as ShapedImageView
iconView.style = adapter.profileImageStyle
} }
override fun onClick(v: View) { override fun onClick(v: View) {
@ -617,7 +621,9 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
fun display(account: AccountDetails) { fun display(account: AccountDetails) {
iconView.setBorderColor(account.color) iconView.setBorderColor(account.color)
adapter.requestManager.loadProfileImage(itemView.context, account).into(iconView) adapter.requestManager.loadProfileImage(itemView.context, account,
adapter.profileImageStyle, iconView.cornerRadius,
iconView.cornerRadiusRatio).into(iconView)
} }
} }

View File

@ -61,8 +61,8 @@ class AccountsManagerFragment : BaseFragment(), LoaderManager.LoaderCallbacks<Li
setHasOptionsMenu(true) setHasOptionsMenu(true)
val am = AccountManager.get(context) val am = AccountManager.get(context)
adapter = AccountDetailsAdapter(context, Glide.with(this)).apply { adapter = AccountDetailsAdapter(context, Glide.with(this)).apply {
setSortEnabled(true) sortEnabled = true
setSwitchEnabled(true) switchEnabled = true
accountToggleListener = { pos, checked -> accountToggleListener = { pos, checked ->
val item = getItem(pos) val item = getItem(pos)
item.activated = checked item.activated = checked

View File

@ -929,7 +929,9 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
itemView.name.screenName = String.format("@%s", status.user_screen_name) itemView.name.screenName = String.format("@%s", status.user_screen_name)
itemView.name.updateText(formatter) itemView.name.updateText(formatter)
adapter.requestManager.loadProfileImage(context, status).into(itemView.profileImage) adapter.requestManager.loadProfileImage(context, status, adapter.profileImageStyle,
itemView.profileImage.cornerRadius, itemView.profileImage.cornerRadiusRatio)
.into(itemView.profileImage)
val typeIconRes = Utils.getUserTypeIconRes(status.user_is_verified, status.user_is_protected) val typeIconRes = Utils.getUserTypeIconRes(status.user_is_verified, status.user_is_protected)
val typeDescriptionRes = Utils.getUserTypeDescriptionRes(status.user_is_verified, status.user_is_protected) val typeDescriptionRes = Utils.getUserTypeDescriptionRes(status.user_is_verified, status.user_is_protected)
@ -1375,7 +1377,7 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
fun displayUser(item: ParcelableUser) { fun displayUser(item: ParcelableUser) {
val context = adapter.context val context = adapter.context
adapter.requestManager.loadProfileImage(context, item).into(profileImageView) adapter.requestManager.loadProfileImage(context, item, adapter.profileImageStyle).into(profileImageView)
} }
override fun onClick(v: View) { override fun onClick(v: View) {

View File

@ -525,7 +525,8 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
val width = if (bannerWidth > 0) bannerWidth else defWidth val width = if (bannerWidth > 0) bannerWidth else defWidth
val requestManager = Glide.with(this) val requestManager = Glide.with(this)
requestManager.loadProfileBanner(context, user, width).into(profileBanner) requestManager.loadProfileBanner(context, user, width).into(profileBanner)
requestManager.loadOriginalProfileImage(context, user, profileImage.style).into(profileImage) requestManager.loadOriginalProfileImage(context, user, profileImage.style,
profileImage.cornerRadius, profileImage.cornerRadiusRatio).into(profileImage)
val relationship = relationship val relationship = relationship
if (relationship == null) { if (relationship == null) {
getFriendship() getFriendship()

View File

@ -42,6 +42,7 @@ import com.twitter.Validator
import kotlinx.android.synthetic.main.fragment_user_profile_editor.* import kotlinx.android.synthetic.main.fragment_user_profile_editor.*
import org.mariotaku.abstask.library.AbstractTask import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.abstask.library.TaskStarter import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.kpreferences.get
import org.mariotaku.microblog.library.MicroBlog import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.model.ProfileUpdate import org.mariotaku.microblog.library.twitter.model.ProfileUpdate
@ -50,6 +51,7 @@ import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.* import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.activity.ColorPickerDialogActivity import org.mariotaku.twidere.activity.ColorPickerDialogActivity
import org.mariotaku.twidere.activity.ThemedMediaPickerActivity import org.mariotaku.twidere.activity.ThemedMediaPickerActivity
import org.mariotaku.twidere.constant.profileImageStyleKey
import org.mariotaku.twidere.extension.loadProfileBanner import org.mariotaku.twidere.extension.loadProfileBanner
import org.mariotaku.twidere.extension.loadProfileImage import org.mariotaku.twidere.extension.loadProfileImage
import org.mariotaku.twidere.extension.model.newMicroBlogInstance import org.mariotaku.twidere.extension.model.newMicroBlogInstance
@ -289,7 +291,7 @@ class UserProfileEditorFragment : BaseFragment(), OnSizeChangedListener, TextWat
editLocation.setText(user.location) editLocation.setText(user.location)
editUrl.setText(if (isEmpty(user.url_expanded)) user.url else user.url_expanded) editUrl.setText(if (isEmpty(user.url_expanded)) user.url else user.url_expanded)
Glide.with(this).loadProfileImage(context, user).into(profileImage) Glide.with(this).loadProfileImage(context, user, 0).into(profileImage)
Glide.with(this).loadProfileBanner(context, user, resources.displayMetrics.widthPixels).into(profileBanner) Glide.with(this).loadProfileBanner(context, user, resources.displayMetrics.widthPixels).into(profileBanner)
Glide.with(this).load(user.profile_background_url).into(profileBackground) Glide.with(this).load(user.profile_background_url).into(profileBackground)

View File

@ -249,8 +249,9 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
val summary = data.getSubtitle(context) val summary = data.getSubtitle(context)
val requestManager = Glide.with(this) val requestManager = Glide.with(this)
requestManager.loadProfileImage(context, data).into(conversationAvatar) val profileImageStyle = preferences[profileImageStyleKey]
requestManager.loadProfileImage(context, data, size = ProfileImageSize.REASONABLY_SMALL).into(appBarIcon) requestManager.loadProfileImage(context, data, profileImageStyle).into(conversationAvatar)
requestManager.loadProfileImage(context, data, profileImageStyle, size = ProfileImageSize.REASONABLY_SMALL).into(appBarIcon)
appBarTitle.text = name appBarTitle.text = name
conversationTitle.text = name conversationTitle.text = name
if (summary != null) { if (summary != null) {

View File

@ -480,7 +480,8 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(conversationTitle, null, TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(conversationTitle, null,
null, stateIcon, null) null, stateIcon, null)
Glide.with(this).loadProfileImage(context, conversation).into(conversationAvatar) Glide.with(this).loadProfileImage(context, conversation, preferences[profileImageStyleKey])
.into(conversationAvatar)
} }
internal class AddMediaTask( internal class AddMediaTask(

View File

@ -50,7 +50,7 @@ class ConnectivityStateReceiver : BroadcastReceiver() {
val appContext = context.applicationContext val appContext = context.applicationContext
val cm = appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val cm = appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val isNetworkMetered = ConnectivityManagerCompat.isActiveNetworkMetered(cm) val isNetworkMetered = ConnectivityManagerCompat.isActiveNetworkMetered(cm)
DependencyHolder.get(context).mediaLoader.isNetworkMetered = isNetworkMetered DependencyHolder.get(context).mediaPreloader.isNetworkMetered = isNetworkMetered
val isCharging = Utils.isCharging(appContext) val isCharging = Utils.isCharging(appContext)
if (!isNetworkMetered && isCharging) { if (!isNetworkMetered && isCharging) {
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()

View File

@ -22,7 +22,7 @@ abstract class BaseAbstractTask<Params, Result, Callback>(val context: Context)
@Inject @Inject
lateinit var microBlogWrapper: AsyncTwitterWrapper lateinit var microBlogWrapper: AsyncTwitterWrapper
@Inject @Inject
lateinit var mediaLoader: MediaLoaderWrapper lateinit var mediaPreloader: MediaPreloader
@Inject @Inject
lateinit var preferences: SharedPreferencesWrapper lateinit var preferences: SharedPreferencesWrapper
@Inject @Inject

View File

@ -133,7 +133,7 @@ abstract class GetActivitiesTask(
val item = activities[i] val item = activities[i]
val activity = ParcelableActivityUtils.fromActivity(item, details.key, false, val activity = ParcelableActivityUtils.fromActivity(item, details.key, false,
profileImageSize) profileImageSize)
mediaLoader.preloadActivity(activity) mediaPreloader.preloadActivity(activity)
activity.position_key = GetStatusesTask.getPositionKey(activity.timestamp, activity.position_key = GetStatusesTask.getPositionKey(activity.timestamp,
activity.timestamp, lastSortId, sortDiff, i, activities.size) activity.timestamp, lastSortId, sortDiff, i, activities.size)
if (deleteBound[0] < 0) { if (deleteBound[0] < 0) {

View File

@ -174,7 +174,7 @@ abstract class GetStatusesTask(
status.position_key = getPositionKey(status.timestamp, status.sort_id, lastSortId, status.position_key = getPositionKey(status.timestamp, status.sort_id, lastSortId,
sortDiff, i, statuses.size) sortDiff, i, statuses.size)
status.inserted_date = System.currentTimeMillis() status.inserted_date = System.currentTimeMillis()
mediaLoader.preloadStatus(status) mediaPreloader.preloadStatus(status)
values[i] = creator.create(status) values[i] = creator.create(status)
if (minIdx == -1 || item < statuses[minIdx]) { if (minIdx == -1 || item < statuses[minIdx]) {
minIdx = i minIdx = i

View File

@ -19,16 +19,19 @@
package org.mariotaku.twidere.util package org.mariotaku.twidere.util
import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import com.bumptech.glide.Glide
import org.mariotaku.kpreferences.get import org.mariotaku.kpreferences.get
import org.mariotaku.twidere.constant.mediaPreloadKey import org.mariotaku.twidere.constant.mediaPreloadKey
import org.mariotaku.twidere.constant.mediaPreloadOnWifiOnlyKey import org.mariotaku.twidere.constant.mediaPreloadOnWifiOnlyKey
import org.mariotaku.twidere.extension.loadProfileImage
import org.mariotaku.twidere.model.ParcelableActivity import org.mariotaku.twidere.model.ParcelableActivity
import org.mariotaku.twidere.model.ParcelableMedia import org.mariotaku.twidere.model.ParcelableMedia
import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.util.getActivityStatus import org.mariotaku.twidere.model.util.getActivityStatus
class MediaLoaderWrapper { class MediaPreloader(val context: Context) {
var isNetworkMetered: Boolean = true var isNetworkMetered: Boolean = true
private var preloadEnabled: Boolean = false private var preloadEnabled: Boolean = false
@ -39,8 +42,7 @@ class MediaLoaderWrapper {
fun preloadStatus(status: ParcelableStatus) { fun preloadStatus(status: ParcelableStatus) {
if (!shouldPreload) return if (!shouldPreload) return
preloadProfileImage(status.user_profile_image_url) Glide.with(context).loadProfileImage(context, status, 0).preload()
preloadProfileImage(status.quoted_user_profile_image)
preloadMedia(status.media) preloadMedia(status.media)
preloadMedia(status.quoted_media) preloadMedia(status.quoted_media)
} }
@ -57,15 +59,16 @@ class MediaLoaderWrapper {
private fun preloadMedia(media: Array<ParcelableMedia>?) { private fun preloadMedia(media: Array<ParcelableMedia>?) {
media?.forEach { item -> media?.forEach { item ->
val url = item.preview_url ?: item.media_url ?: return@forEach val url = item.preview_url ?: run {
if (item.type != ParcelableMedia.Type.IMAGE) return@run null
return@run item.media_url
} ?: return@forEach
preloadPreviewImage(url) preloadPreviewImage(url)
} }
} }
private fun preloadProfileImage(url: String?) {
}
private fun preloadPreviewImage(url: String?) { private fun preloadPreviewImage(url: String?) {
Glide.with(context).load(url).preload()
} }
} }

View File

@ -182,12 +182,12 @@ class ApplicationModule(private val application: Application) {
@Provides @Provides
@Singleton @Singleton
fun mediaLoaderWrapper(preferences: SharedPreferencesWrapper): MediaLoaderWrapper { fun mediaLoaderWrapper(preferences: SharedPreferencesWrapper): MediaPreloader {
val wrapper = MediaLoaderWrapper() val preloader = MediaPreloader(application)
wrapper.reloadOptions(preferences) preloader.reloadOptions(preferences)
val cm = application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val cm = application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
wrapper.isNetworkMetered = ConnectivityManagerCompat.isActiveNetworkMetered(cm) preloader.isNetworkMetered = ConnectivityManagerCompat.isActiveNetworkMetered(cm)
return wrapper return preloader
} }
@Provides @Provides

View File

@ -69,7 +69,7 @@ class DependencyHolder internal constructor(context: Context) {
lateinit var defaultFeatures: DefaultFeatures lateinit var defaultFeatures: DefaultFeatures
internal set internal set
@Inject @Inject
lateinit var mediaLoader: MediaLoaderWrapper lateinit var mediaPreloader: MediaPreloader
internal set internal set
@Inject @Inject
lateinit var userColorNameManager: UserColorNameManager lateinit var userColorNameManager: UserColorNameManager

View File

@ -0,0 +1,72 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.util.glide
import android.content.Context
import android.graphics.*
import com.bumptech.glide.Glide
import com.bumptech.glide.load.Transformation
import com.bumptech.glide.load.engine.Resource
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapResource
class RoundedRectTransformation(
private val bitmapPool: BitmapPool,
private val radius: Float,
private val radiusPercent: Float
) : Transformation<Bitmap> {
val rectF = RectF()
constructor(context: Context, radius: Float, radiusPercent: Float) :
this(Glide.get(context).bitmapPool, radius, radiusPercent)
override fun transform(resource: Resource<Bitmap>, outWidth: Int, outHeight: Int): Resource<Bitmap> {
val source = resource.get()
val width = source.width
val height = source.height
val bitmap = bitmapPool.get(width, height, Bitmap.Config.ARGB_8888)
?: Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val paint = Paint()
paint.isAntiAlias = true
paint.shader = BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
rectF.right = width.toFloat()
rectF.bottom = height.toFloat()
val calculatedRadius = if (radiusPercent != 0f) {
width * radiusPercent
} else {
radius
}
drawRoundRect(canvas, calculatedRadius, paint)
return BitmapResource.obtain(bitmap, bitmapPool)
}
private fun drawRoundRect(canvas: Canvas, radius: Float, paint: Paint) {
canvas.drawRoundRect(rectF, radius, radius, paint)
}
override fun getId(): String {
return "RoundedRectTransformation(radius=$radius, radiusPercent=$radius)"
}
}

View File

@ -0,0 +1,79 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.view.holder
import android.support.v7.widget.RecyclerView
import android.view.View
import android.widget.CompoundButton
import android.widget.ImageView
import android.widget.TextView
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.AccountDetailsAdapter
import org.mariotaku.twidere.extension.loadProfileImage
import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.view.ProfileImageView
import org.mariotaku.twidere.view.iface.IColorLabelView
class AccountViewHolder(
itemView: View,
val adapter: AccountDetailsAdapter
) : RecyclerView.ViewHolder(itemView) {
private val content = itemView as IColorLabelView
private val name = itemView.findViewById(android.R.id.text1) as TextView
private val screenName = itemView.findViewById(android.R.id.text2) as TextView
private val profileImage = itemView.findViewById(android.R.id.icon) as ProfileImageView
private val toggle = itemView.findViewById(android.R.id.toggle) as CompoundButton
private val toggleContainer = itemView.findViewById(R.id.toggle_container)
private val accountType = itemView.findViewById(R.id.account_type) as ImageView
private val dragHandle = itemView.findViewById(R.id.drag_handle)
init {
profileImage.style = adapter.profileImageStyle
}
fun setAccountColor(color: Int) {
content.drawEnd(color)
}
fun setSortEnabled(enabled: Boolean) {
dragHandle.visibility = if (enabled) View.VISIBLE else View.GONE
}
fun display(details: AccountDetails) {
name.text = details.user.name
screenName.text = String.format("@%s", details.user.screen_name)
setAccountColor(details.color)
if (adapter.profileImageEnabled) {
adapter.requestManager.loadProfileImage(adapter.context, details, adapter.profileImageStyle,
profileImage.cornerRadius, profileImage.cornerRadiusRatio).into(profileImage)
} else {
// TODO: display stub image?
}
accountType.setImageResource(AccountUtils.getAccountTypeIcon(details.type))
toggle.isChecked = details.activated
toggle.setOnCheckedChangeListener(adapter.checkedChangeListener)
toggle.tag = layoutPosition
toggleContainer.visibility = if (adapter.switchEnabled) View.VISIBLE else View.GONE
setSortEnabled(adapter.sortEnabled)
}
}

View File

@ -135,7 +135,8 @@ class ActivityTitleSummaryViewHolder(
if (i < length) { if (i < length) {
view.visibility = View.VISIBLE view.visibility = View.VISIBLE
val context = adapter.context val context = adapter.context
adapter.requestManager.loadProfileImage(context, users[i]).into(view) adapter.requestManager.loadProfileImage(context, users[i], adapter.profileImageStyle)
.into(view)
} else { } else {
view.visibility = View.GONE view.visibility = View.GONE
} }

View File

@ -32,6 +32,7 @@ import org.mariotaku.twidere.model.ParcelableMedia
import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.util.ParcelableMediaUtils import org.mariotaku.twidere.model.util.ParcelableMediaUtils
import org.mariotaku.twidere.view.ProfileImageView
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder
class MediaStatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View) : RecyclerView.ViewHolder(itemView), IStatusViewHolder, View.OnClickListener, View.OnLongClickListener { class MediaStatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View) : RecyclerView.ViewHolder(itemView), IStatusViewHolder, View.OnClickListener, View.OnLongClickListener {
@ -41,7 +42,7 @@ class MediaStatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView:
private val mediaImageContainer = itemView.mediaImageContainer private val mediaImageContainer = itemView.mediaImageContainer
private val mediaImageView = itemView.mediaImage private val mediaImageView = itemView.mediaImage
override val profileImageView: ImageView = itemView.mediaProfileImage override val profileImageView: ProfileImageView = itemView.mediaProfileImage
private val mediaTextView = itemView.mediaText private val mediaTextView = itemView.mediaText
private var listener: IStatusViewHolder.StatusClickListener? = null private var listener: IStatusViewHolder.StatusClickListener? = null
@ -80,7 +81,9 @@ class MediaStatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView:
mediaImageView.setHasPlayIcon(ParcelableMediaUtils.hasPlayIcon(firstMedia.type)) mediaImageView.setHasPlayIcon(ParcelableMediaUtils.hasPlayIcon(firstMedia.type))
val context = itemView.context val context = itemView.context
adapter.requestManager.loadProfileImage(context, status).into(profileImageView) adapter.requestManager.loadProfileImage(context, status,
adapter.profileImageStyle, profileImageView.cornerRadius,
profileImageView.cornerRadiusRatio).into(profileImageView)
// TODO image loaded event and credentials // TODO image loaded event and credentials
adapter.requestManager.load(firstMedia.preview_url).into(mediaImageView) adapter.requestManager.load(firstMedia.preview_url).into(mediaImageView)
} }

View File

@ -34,7 +34,8 @@ open class SimpleUserViewHolder<out A : IContentAdapter>(
secondaryNameView.text = "@${user.screen_name}" secondaryNameView.text = "@${user.screen_name}"
if (adapter.profileImageEnabled) { if (adapter.profileImageEnabled) {
val context = itemView.context val context = itemView.context
adapter.requestManager.loadProfileImage(context, user).into(profileImageView) adapter.requestManager.loadProfileImage(context, user, adapter.profileImageStyle)
.into(profileImageView)
profileImageView.visibility = View.VISIBLE profileImageView.visibility = View.VISIBLE
} else { } else {
profileImageView.visibility = View.GONE profileImageView.visibility = View.GONE

View File

@ -101,7 +101,9 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
profileImageView.visibility = if (profileImageEnabled) View.VISIBLE else View.GONE profileImageView.visibility = if (profileImageEnabled) View.VISIBLE else View.GONE
statusContentUpperSpace.visibility = View.VISIBLE statusContentUpperSpace.visibility = View.VISIBLE
profileImageView.setImageResource(R.drawable.ic_profile_image_twidere) adapter.requestManager.loadProfileImage(itemView.context, R.drawable.ic_profile_image_twidere,
adapter.profileImageStyle, profileImageView.cornerRadius,
profileImageView.cornerRadiusRatio).into(profileImageView)
nameView.name = TWIDERE_PREVIEW_NAME nameView.name = TWIDERE_PREVIEW_NAME
nameView.screenName = "@" + TWIDERE_PREVIEW_SCREEN_NAME nameView.screenName = "@" + TWIDERE_PREVIEW_SCREEN_NAME
nameView.updateText(adapter.bidiFormatter) nameView.updateText(adapter.bidiFormatter)
@ -297,7 +299,9 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
if (adapter.profileImageEnabled) { if (adapter.profileImageEnabled) {
profileImageView.visibility = View.VISIBLE profileImageView.visibility = View.VISIBLE
requestManager.loadProfileImage(context, status).into(profileImageView) requestManager.loadProfileImage(context, status, adapter.profileImageStyle,
profileImageView.cornerRadius, profileImageView.cornerRadiusRatio)
.into(profileImageView)
profileTypeView.setImageResource(getUserTypeIconRes(status.user_is_verified, status.user_is_protected)) profileTypeView.setImageResource(getUserTypeIconRes(status.user_is_verified, status.user_is_protected))
profileTypeView.visibility = View.VISIBLE profileTypeView.visibility = View.VISIBLE

View File

@ -135,7 +135,8 @@ class UserViewHolder(
if (adapter.profileImageEnabled) { if (adapter.profileImageEnabled) {
profileImageView.visibility = View.VISIBLE profileImageView.visibility = View.VISIBLE
adapter.requestManager.loadProfileImage(context, user).into(profileImageView) adapter.requestManager.loadProfileImage(context, user, adapter.profileImageStyle,
profileImageView.cornerRadius, profileImageView.cornerRadiusRatio).into(profileImageView)
} else { } else {
profileImageView.visibility = View.GONE profileImageView.visibility = View.GONE
} }

View File

@ -83,7 +83,8 @@ abstract class AbsMessageViewHolder(itemView: View, val adapter: MessagesConvers
if (adapter.displaySenderProfile && adapter.profileImageEnabled && sender != null if (adapter.displaySenderProfile && adapter.profileImageEnabled && sender != null
&& !message.is_outgoing) { && !message.is_outgoing) {
this.visibility = View.VISIBLE this.visibility = View.VISIBLE
adapter.requestManager.loadProfileImage(context, sender).into(this) adapter.requestManager.loadProfileImage(context, sender,
adapter.profileImageStyle).into(this)
} else { } else {
this.visibility = View.GONE this.visibility = View.GONE
} }

View File

@ -49,7 +49,20 @@ class MessageEntryViewHolder(itemView: View, val adapter: MessagesEntriesAdapter
private val unreadCount by lazy { itemView.unreadCount } private val unreadCount by lazy { itemView.unreadCount }
init { init {
setup() val textSize = adapter.textSize
name.setPrimaryTextSize(textSize * 1.05f)
name.setSecondaryTextSize(textSize * 0.95f)
text.textSize = textSize
time.textSize = textSize * 0.85f
profileImage.style = adapter.profileImageStyle
itemView.setOnClickListener {
adapter.listener?.onConversationClick(layoutPosition)
}
profileImage.setOnClickListener {
adapter.listener?.onProfileImageClick(layoutPosition)
}
} }
fun display(conversation: ParcelableMessageConversation) { fun display(conversation: ParcelableMessageConversation) {
@ -83,7 +96,9 @@ class MessageEntryViewHolder(itemView: View, val adapter: MessagesEntriesAdapter
} else { } else {
stateIndicator.visibility = View.GONE stateIndicator.visibility = View.GONE
} }
adapter.requestManager.loadProfileImage(adapter.context, conversation).into(profileImage) adapter.requestManager.loadProfileImage(adapter.context, conversation,
adapter.profileImageStyle, profileImage.cornerRadius,
profileImage.cornerRadiusRatio).into(profileImage)
if (conversation.unread_count > 0) { if (conversation.unread_count > 0) {
unreadCount.visibility = View.VISIBLE unreadCount.visibility = View.VISIBLE
unreadCount.text = conversation.unread_count.toString() unreadCount.text = conversation.unread_count.toString()
@ -92,21 +107,6 @@ class MessageEntryViewHolder(itemView: View, val adapter: MessagesEntriesAdapter
} }
} }
private fun setup() {
val textSize = adapter.textSize
name.setPrimaryTextSize(textSize * 1.05f)
name.setSecondaryTextSize(textSize * 0.95f)
text.textSize = textSize
time.textSize = textSize * 0.85f
itemView.setOnClickListener {
adapter.listener?.onConversationClick(layoutPosition)
}
profileImage.setOnClickListener {
adapter.listener?.onProfileImageClick(layoutPosition)
}
}
companion object { companion object {
const val layoutResource = R.layout.list_item_message_entry const val layoutResource = R.layout.list_item_message_entry
} }