diff --git a/app/build.gradle b/app/build.gradle index 895f5f78c..05e1259db 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -245,15 +245,8 @@ dependencies { implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'com.github.wooplr:Spotlight:1.3' implementation 'com.google.code.findbugs:jsr305:3.0.2' - implementation('com.github.nextcloud-deps:ChatKit:0.3.0-1', { - exclude group: 'com.facebook.fresco' - }) + implementation 'com.github.nextcloud-deps:ChatKit:0.3.1' - implementation 'com.github.nextcloud-deps.fresco:fresco:v111' - implementation 'com.github.nextcloud-deps.fresco:animated-webp:v111' - implementation 'com.github.nextcloud-deps.fresco:webpsupport:v111' - implementation 'com.github.nextcloud-deps.fresco:animated-gif:v111' - implementation 'com.github.nextcloud-deps.fresco:imagepipeline-okhttp3:v111' implementation 'joda-time:joda-time:2.12.2' implementation "io.coil-kt:coil:${coilKtVersion}" implementation "io.coil-kt:coil-gif:${coilKtVersion}" diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java index cbd377bc9..95829d10b 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java +++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java @@ -149,7 +149,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.content.res.AppCompatResources; import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import autodagger.AutoInjector; @@ -540,20 +539,16 @@ public class CallActivity extends CallBaseActivity { private void updateAudioOutputButton(WebRtcAudioManager.AudioDevice activeAudioDevice) { switch (activeAudioDevice) { case BLUETOOTH: - binding.audioOutputButton.getHierarchy().setPlaceholderImage( - AppCompatResources.getDrawable(context, R.drawable.ic_baseline_bluetooth_audio_24)); + binding.audioOutputButton.setImageResource ( R.drawable.ic_baseline_bluetooth_audio_24); break; case SPEAKER_PHONE: - binding.audioOutputButton.getHierarchy().setPlaceholderImage( - AppCompatResources.getDrawable(context, R.drawable.ic_volume_up_white_24dp)); + binding.audioOutputButton.setImageResource(R.drawable.ic_volume_up_white_24dp); break; case EARPIECE: - binding.audioOutputButton.getHierarchy().setPlaceholderImage( - AppCompatResources.getDrawable(context, R.drawable.ic_baseline_phone_in_talk_24)); + binding.audioOutputButton.setImageResource(R.drawable.ic_baseline_phone_in_talk_24); break; case WIRED_HEADSET: - binding.audioOutputButton.getHierarchy().setPlaceholderImage( - AppCompatResources.getDrawable(context, R.drawable.ic_baseline_headset_mic_24)); + binding.audioOutputButton.setImageResource(R.drawable.ic_baseline_headset_mic_24); break; default: Log.e(TAG, "Icon for audio output not available"); @@ -795,7 +790,7 @@ public class CallActivity extends CallBaseActivity { onCameraClick(); } } else { - binding.cameraButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_off_white_24px); + binding.cameraButton.setImageResource(R.drawable.ic_videocam_off_white_24px); binding.cameraButton.setAlpha(0.7f); binding.switchSelfVideoButton.setVisibility(View.GONE); } @@ -806,7 +801,7 @@ public class CallActivity extends CallBaseActivity { onMicrophoneClick(); } } else { - binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); + binding.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px); } if (!isConnectionEstablished()) { @@ -917,7 +912,7 @@ public class CallActivity extends CallBaseActivity { if (!canPublishAudioStream) { microphoneOn = false; - binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); + binding.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px); toggleMedia(false, false); } @@ -961,12 +956,12 @@ public class CallActivity extends CallBaseActivity { microphoneOn = !microphoneOn; if (microphoneOn) { - binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_white_24px); + binding.microphoneButton.setImageResource(R.drawable.ic_mic_white_24px); updatePictureInPictureActions(R.drawable.ic_mic_white_24px, getResources().getString(R.string.nc_pip_microphone_mute), MICROPHONE_PIP_REQUEST_MUTE); } else { - binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); + binding.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px); updatePictureInPictureActions(R.drawable.ic_mic_off_white_24px, getResources().getString(R.string.nc_pip_microphone_unmute), MICROPHONE_PIP_REQUEST_UNMUTE); @@ -974,7 +969,7 @@ public class CallActivity extends CallBaseActivity { toggleMedia(microphoneOn, false); } else { - binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_white_24px); + binding.microphoneButton.setImageResource(R.drawable.ic_mic_white_24px); pulseAnimation.start(); toggleMedia(true, false); } @@ -997,7 +992,7 @@ public class CallActivity extends CallBaseActivity { if (!canPublishVideoStream) { videoOn = false; - binding.cameraButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_off_white_24px); + binding.cameraButton.setImageResource(R.drawable.ic_videocam_off_white_24px); binding.switchSelfVideoButton.setVisibility(View.GONE); return; } @@ -1006,12 +1001,12 @@ public class CallActivity extends CallBaseActivity { videoOn = !videoOn; if (videoOn) { - binding.cameraButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_white_24px); + binding.cameraButton.setImageResource(R.drawable.ic_videocam_white_24px); if (cameraEnumerator.getDeviceNames().length > 1) { binding.switchSelfVideoButton.setVisibility(View.VISIBLE); } } else { - binding.cameraButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_off_white_24px); + binding.cameraButton.setImageResource(R.drawable.ic_videocam_off_white_24px); binding.switchSelfVideoButton.setVisibility(View.GONE); } @@ -2675,7 +2670,7 @@ public class CallActivity extends CallBaseActivity { v.onTouchEvent(event); if (event.getAction() == MotionEvent.ACTION_UP && isPushToTalkActive) { isPushToTalkActive = false; - binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); + binding.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px); pulseAnimation.stop(); toggleMedia(false, false); animateCallControls(false, 5000); diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.kt index f81389f67..6a6ed114e 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.kt @@ -28,8 +28,6 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.res.Configuration -import android.graphics.Bitmap -import android.graphics.drawable.BitmapDrawable import android.media.AudioAttributes import android.media.MediaPlayer import android.os.Build @@ -41,24 +39,18 @@ import android.view.View import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import autodagger.AutoInjector -import com.facebook.common.executors.UiThreadImmediateExecutorService -import com.facebook.common.references.CloseableReference -import com.facebook.datasource.DataSource -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber -import com.facebook.imagepipeline.image.CloseableImage import com.nextcloud.talk.R import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.CallNotificationActivityBinding +import com.nextcloud.talk.extensions.loadAvatar import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.models.json.participants.ParticipantsOverall import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DoNotDisturbUtils.shouldPlaySound import com.nextcloud.talk.utils.NotificationUtils import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri @@ -75,6 +67,7 @@ import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers +import kotlinx.android.synthetic.main.call_item.* import okhttp3.Cache import org.parceler.Parcels import java.io.IOException @@ -356,7 +349,7 @@ class CallNotificationActivity : CallBaseActivity() { private fun setUpAfterConversationIsKnown() { binding!!.conversationNameTextView.text = currentConversation!!.displayName if (currentConversation!!.type === Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) { - setAvatarForOneToOneCall() + avatarImageView.loadAvatar(userBeingCalled!!, currentConversation!!.name!!) } else { binding!!.avatarImageView.setImageResource(R.drawable.ic_circular_group) } @@ -364,34 +357,6 @@ class CallNotificationActivity : CallBaseActivity() { showAnswerControls() } - @Suppress("MagicNumber") - private fun setAvatarForOneToOneCall() { - val imageRequest = DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - userBeingCalled!!.baseUrl, - currentConversation!!.name, - true - ) - ) - val imagePipeline = Fresco.getImagePipeline() - val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null) - dataSource.subscribe( - object : BaseBitmapDataSubscriber() { - override fun onNewResultImpl(bitmap: Bitmap?) { - binding!!.avatarImageView.hierarchy.setImage( - BitmapDrawable(resources, bitmap), 100f, - true - ) - } - - override fun onFailureImpl(dataSource: DataSource>) { - Log.e(TAG, "failed to load avatar") - } - }, - UiThreadImmediateExecutorService.getInstance() - ) - } - private fun endMediaNotifications() { if (mediaPlayer != null) { if (mediaPlayer!!.isPlaying) { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/ParticipantsAdapter.java b/app/src/main/java/com/nextcloud/talk/adapters/ParticipantsAdapter.java index bd09717d6..b8b5cc60b 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/ParticipantsAdapter.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/ParticipantsAdapter.java @@ -12,12 +12,9 @@ import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.interfaces.DraweeController; -import com.facebook.drawee.view.SimpleDraweeView; import com.nextcloud.talk.R; import com.nextcloud.talk.activities.CallActivity; -import com.nextcloud.talk.utils.DisplayUtils; +import com.nextcloud.talk.extensions.ImageViewExtensionsKt; import org.webrtc.MediaStream; import org.webrtc.MediaStreamTrack; @@ -109,7 +106,7 @@ public class ParticipantsAdapter extends BaseAdapter { convertView.setLayoutParams(layoutParams); TextView nickTextView = convertView.findViewById(R.id.peer_nick_text_view); - SimpleDraweeView imageView = convertView.findViewById(R.id.avatarImageView); + ImageView imageView = convertView.findViewById(R.id.avatarImageView); MediaStream mediaStream = participantDisplayItem.getMediaStream(); if (hasVideoStream(participantDisplayItem, mediaStream)) { @@ -128,13 +125,7 @@ public class ParticipantsAdapter extends BaseAdapter { nickTextView.setVisibility(View.VISIBLE); nickTextView.setText(participantDisplayItem.getNick()); } - - imageView.setController(null); - DraweeController draweeController = Fresco.newDraweeControllerBuilder() - .setOldController(imageView.getController()) - .setImageRequest(DisplayUtils.getImageRequestForUrl(participantDisplayItem.getUrlForAvatar())) - .build(); - imageView.setController(draweeController); + ImageViewExtensionsKt.loadAvatarWithUrl(imageView,null, participantDisplayItem.getUrlForAvatar()); } ImageView audioOffView = convertView.findViewById(R.id.remote_audio_off); diff --git a/app/src/main/java/com/nextcloud/talk/adapters/ReactionsAdapter.kt b/app/src/main/java/com/nextcloud/talk/adapters/ReactionsAdapter.kt index 0a3ff60f7..551e15a26 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/ReactionsAdapter.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/ReactionsAdapter.kt @@ -34,7 +34,7 @@ class ReactionsAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReactionsViewHolder { val itemBinding = ReactionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return ReactionsViewHolder(itemBinding, user?.baseUrl) + return ReactionsViewHolder(itemBinding, user) } override fun onBindViewHolder(holder: ReactionsViewHolder, position: Int) { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/ReactionsViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/ReactionsViewHolder.kt index facc1cd49..2f9071ae0 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/ReactionsViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/ReactionsViewHolder.kt @@ -22,18 +22,17 @@ package com.nextcloud.talk.adapters import android.text.TextUtils import androidx.recyclerview.widget.RecyclerView -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.interfaces.DraweeController import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.ReactionItemBinding +import com.nextcloud.talk.extensions.loadAvatar +import com.nextcloud.talk.extensions.loadGuestAvatar import com.nextcloud.talk.models.json.reactions.ReactionVoter -import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils class ReactionsViewHolder( private val binding: ReactionItemBinding, - private val baseUrl: String? + private val user: User? ) : RecyclerView.ViewHolder(binding.root) { fun bind(reactionItem: ReactionItem, clickListener: ReactionItemClickListener) { @@ -41,7 +40,7 @@ class ReactionsViewHolder( binding.reaction.text = reactionItem.reaction binding.name.text = reactionItem.reactionVoter.actorDisplayName - if (baseUrl != null && baseUrl.isNotEmpty()) { + if (user != null && user.baseUrl?.isNotEmpty() == true) { loadAvatar(reactionItem) } } @@ -52,35 +51,13 @@ class ReactionsViewHolder( if (!TextUtils.isEmpty(reactionItem.reactionVoter.actorDisplayName)) { displayName = reactionItem.reactionVoter.actorDisplayName!! } - val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() - .setOldController(binding.avatar.controller) - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForGuestAvatar( - baseUrl, - displayName, - false - ) - ) - ) - .build() - binding.avatar.controller = draweeController + binding.avatar.loadGuestAvatar(user!!.baseUrl!!, displayName!!, false) } else if (reactionItem.reactionVoter.actorType == ReactionVoter.ReactionActorType.USERS) { - val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() - .setOldController(binding.avatar.controller) - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - baseUrl, - reactionItem.reactionVoter.actorId, - false - ) - ) - ) - .build() - binding.avatar.controller = draweeController + binding.avatar.loadAvatar( + user!!, + reactionItem.reactionVoter.actorId!!, + false + ) } } } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.java index ad4179e88..36f652da1 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.java @@ -27,15 +27,12 @@ import android.net.Uri; import android.text.TextUtils; import android.view.View; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.interfaces.DraweeController; import com.nextcloud.talk.R; import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.databinding.AccountItemBinding; +import com.nextcloud.talk.extensions.ImageViewExtensionsKt; import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.ui.theme.ViewThemeUtils; -import com.nextcloud.talk.utils.ApiUtils; -import com.nextcloud.talk.utils.DisplayUtils; import java.util.List; import java.util.regex.Pattern; @@ -108,7 +105,6 @@ public class AdvancedUserItem extends AbstractFlexibleItem= Build.VERSION_CODES.O) { - holder.binding.avatarDraweeView.getHierarchy().setPlaceholderImage( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar(holder.binding.avatarDraweeView, - roundPlaceholderDrawable))); + avatar = viewThemeUtils.talk.themePlaceholderAvatar( + holder.binding.avatarView, + roundPlaceholderDrawable + ); + } else { - holder.binding.avatarDraweeView.setImageResource(fallbackImageResource); + avatar = fallbackImageResource; } + + ImageViewExtensionsKt.loadAvatar(holder.binding.avatarView, avatar); } @Override diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt index c129852dc..4b16ed6af 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt @@ -29,27 +29,26 @@ import android.annotation.SuppressLint import android.content.Context import android.content.res.ColorStateList import android.graphics.Typeface -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable import android.os.Build import android.text.TextUtils import android.text.format.DateUtils import android.view.View import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.interfaces.DraweeController import com.nextcloud.talk.R import com.nextcloud.talk.adapters.items.ConversationItem.ConversationItemViewHolder import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.RvItemConversationWithLastMessageBinding +import com.nextcloud.talk.extensions.loadAvatar +import com.nextcloud.talk.extensions.loadGroupCallAvatar +import com.nextcloud.talk.extensions.loadPublicCallAvatar +import com.nextcloud.talk.extensions.loadSystemAvatar import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType import com.nextcloud.talk.ui.StatusDrawable import com.nextcloud.talk.ui.theme.ViewThemeUtils -import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability import eu.davidea.flexibleadapter.FlexibleAdapter @@ -113,7 +112,6 @@ class ConversationItem( payloads: List ) { val appContext = sharedApplication!!.applicationContext - holder.binding.dialogAvatar.controller = null holder.binding.dialogName.setTextColor( ResourcesCompat.getColor( context.resources, @@ -130,40 +128,7 @@ class ConversationItem( holder.binding.dialogName.text = model.displayName } if (model.unreadMessages > 0) { - holder.binding.dialogName.setTypeface(holder.binding.dialogName.typeface, Typeface.BOLD) - holder.binding.dialogLastMessage.setTypeface(holder.binding.dialogLastMessage.typeface, Typeface.BOLD) - holder.binding.dialogUnreadBubble.visibility = View.VISIBLE - if (model.unreadMessages < 1000) { - holder.binding.dialogUnreadBubble.text = model.unreadMessages.toLong().toString() - } else { - holder.binding.dialogUnreadBubble.setText(R.string.tooManyUnreadMessages) - } - val lightBubbleFillColor = ColorStateList.valueOf( - ContextCompat.getColor( - context, - R.color.conversation_unread_bubble - ) - ) - val lightBubbleTextColor = ContextCompat.getColor( - context, - R.color.conversation_unread_bubble_text - ) - if (model.type === ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) { - viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble) - } else if (model.unreadMention) { - if (hasSpreedFeatureCapability(user, "direct-mention-flag")) { - if (model.unreadMentionDirect!!) { - viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble) - } else { - viewThemeUtils.material.colorChipOutlined(holder.binding.dialogUnreadBubble, 6.0f) - } - } else { - viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble) - } - } else { - holder.binding.dialogUnreadBubble.chipBackgroundColor = lightBubbleFillColor - holder.binding.dialogUnreadBubble.setTextColor(lightBubbleTextColor) - } + showUnreadMessages(holder) } else { holder.binding.dialogName.setTypeface(null, Typeface.NORMAL) holder.binding.dialogDate.setTypeface(null, Typeface.NORMAL) @@ -175,13 +140,13 @@ class ConversationItem( } else { holder.binding.favoriteConversationImageView.visibility = View.GONE } - if (model != null && ConversationType.ROOM_SYSTEM !== model.type) { + if (ConversationType.ROOM_SYSTEM !== model.type) { val size = DisplayUtils.convertDpToPixel(STATUS_SIZE_IN_DP, appContext) holder.binding.userStatusImage.visibility = View.VISIBLE holder.binding.userStatusImage.setImageDrawable( StatusDrawable( model.status, - model.status, + model.statusIcon, size, context.resources.getColor(R.color.bg_default), appContext @@ -190,10 +155,77 @@ class ConversationItem( } else { holder.binding.userStatusImage.visibility = View.GONE } + setLastMessage(holder, appContext) + showAvatar(holder) + } + + private fun showAvatar(holder: ConversationItemViewHolder) { + holder.binding.dialogAvatar.visibility = View.VISIBLE + var shouldLoadAvatar = shouldLoadAvatar(holder) + if (ConversationType.ROOM_SYSTEM == model.type) { + holder.binding.dialogAvatar.loadSystemAvatar() + shouldLoadAvatar = false + } + if (shouldLoadAvatar) { + when (model.type) { + ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(model.name)) { + holder.binding.dialogAvatar.loadAvatar(user, model.name!!) + } else { + holder.binding.dialogAvatar.visibility = View.GONE + } + ConversationType.ROOM_GROUP_CALL -> + holder.binding.dialogAvatar.loadGroupCallAvatar(viewThemeUtils) + ConversationType.ROOM_PUBLIC_CALL -> + holder.binding.dialogAvatar.loadPublicCallAvatar(viewThemeUtils) + else -> holder.binding.dialogAvatar.visibility = View.GONE + } + } + } + + private fun shouldLoadAvatar( + holder: ConversationItemViewHolder + ): Boolean { + var objectType: String? + var returnValue = true + if (!TextUtils.isEmpty(model.objectType.also { objectType = it })) { + when (objectType) { + "share:password" -> { + holder.binding.dialogAvatar.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.ic_circular_lock + ) + ) + returnValue = false + } + "file" -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + holder.binding.dialogAvatar.loadAvatar( + viewThemeUtils.talk.themePlaceholderAvatar( + holder.binding.dialogAvatar, + R.drawable.ic_avatar_document + ) + ) + } else { + holder.binding.dialogAvatar.loadAvatar( + R.drawable.ic_circular_document + ) + } + returnValue = false + } + } + } + return returnValue + } + + private fun setLastMessage( + holder: ConversationItemViewHolder, + appContext: Context + ) { if (model.lastMessage != null) { holder.binding.dialogDate.visibility = View.VISIBLE holder.binding.dialogDate.text = DateUtils.getRelativeTimeSpanString( - model.lastActivity * 1000L, + model.lastActivity * MILLIES, System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE @@ -204,30 +236,13 @@ class ConversationItem( holder.binding.dialogLastMessage.text = model.lastMessage!!.text } else { model.lastMessage!!.activeUser = user - val text: String - if (model.lastMessage!!.getCalculateMessageType() === ChatMessage.MessageType.REGULAR_TEXT_MESSAGE) { - if (model.lastMessage!!.actorId == user.userId) { - text = String.format( - appContext.getString(R.string.nc_formatted_message_you), - model.lastMessage!!.lastMessageDisplayText - ) - } else { - val authorDisplayName = - if (!TextUtils.isEmpty(model.lastMessage!!.actorDisplayName)) { - model.lastMessage!!.actorDisplayName - } else if ("guests" == model.lastMessage!!.actorType) { - appContext.getString(R.string.nc_guest) - } else { - "" - } - text = String.format( - appContext.getString(R.string.nc_formatted_message), - authorDisplayName, - model.lastMessage!!.lastMessageDisplayText - ) - } + + val text = if (model.lastMessage!!.getCalculateMessageType() === ChatMessage.MessageType + .REGULAR_TEXT_MESSAGE + ) { + calculateRegularLastMessageText(appContext) } else { - text = model.lastMessage!!.lastMessageDisplayText + model.lastMessage!!.lastMessageDisplayText } holder.binding.dialogLastMessage.text = text } @@ -235,105 +250,68 @@ class ConversationItem( holder.binding.dialogDate.visibility = View.GONE holder.binding.dialogLastMessage.setText(R.string.nc_no_messages_yet) } - holder.binding.dialogAvatar.visibility = View.VISIBLE - var shouldLoadAvatar = true - var objectType: String? - if (!TextUtils.isEmpty(model.objectType.also { objectType = it })) { - when (objectType) { - "share:password" -> { - shouldLoadAvatar = false - holder.binding.dialogAvatar.setImageDrawable( - ContextCompat.getDrawable( - context, - R.drawable.ic_circular_lock - ) + } + + private fun calculateRegularLastMessageText(appContext: Context): String { + return if (model.lastMessage!!.actorId == user.userId) { + String.format( + appContext.getString(R.string.nc_formatted_message_you), + model.lastMessage!!.lastMessageDisplayText + ) + } else { + val authorDisplayName = + if (!TextUtils.isEmpty(model.lastMessage!!.actorDisplayName)) { + model.lastMessage!!.actorDisplayName + } else if ("guests" == model.lastMessage!!.actorType) { + appContext.getString(R.string.nc_guest) + } else { + "" + } + String.format( + appContext.getString(R.string.nc_formatted_message), + authorDisplayName, + model.lastMessage!!.lastMessageDisplayText + ) + } + } + + private fun showUnreadMessages(holder: ConversationItemViewHolder) { + holder.binding.dialogName.setTypeface(holder.binding.dialogName.typeface, Typeface.BOLD) + holder.binding.dialogLastMessage.setTypeface(holder.binding.dialogLastMessage.typeface, Typeface.BOLD) + holder.binding.dialogUnreadBubble.visibility = View.VISIBLE + if (model.unreadMessages < UNREAD_MESSAGES_TRESHOLD) { + holder.binding.dialogUnreadBubble.text = model.unreadMessages.toLong().toString() + } else { + holder.binding.dialogUnreadBubble.setText(R.string.tooManyUnreadMessages) + } + val lightBubbleFillColor = ColorStateList.valueOf( + ContextCompat.getColor( + context, + R.color.conversation_unread_bubble + ) + ) + val lightBubbleTextColor = ContextCompat.getColor( + context, + R.color.conversation_unread_bubble_text + ) + if (model.type === ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) { + viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble) + } else if (model.unreadMention) { + if (hasSpreedFeatureCapability(user, "direct-mention-flag")) { + if (model.unreadMentionDirect!!) { + viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble) + } else { + viewThemeUtils.material.colorChipOutlined( + holder.binding.dialogUnreadBubble, + UNREAD_BUBBLE_STROKE_WIDTH ) } - "file" -> { - shouldLoadAvatar = false - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - holder.binding.dialogAvatar.setImageDrawable( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar( - holder.binding.dialogAvatar, - R.drawable.ic_avatar_document - ) - ) - ) - } else { - holder.binding.dialogAvatar.setImageDrawable( - ContextCompat.getDrawable(context, R.drawable.ic_circular_document) - ) - } - } - else -> {} - } - } - if (ConversationType.ROOM_SYSTEM == model.type) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val layers = arrayOfNulls(2) - layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background) - layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground) - val layerDrawable = LayerDrawable(layers) - holder.binding.dialogAvatar.hierarchy.setPlaceholderImage( - DisplayUtils.getRoundedDrawable(layerDrawable) - ) } else { - holder.binding.dialogAvatar.hierarchy.setPlaceholderImage(R.mipmap.ic_launcher) - } - shouldLoadAvatar = false - } - if (shouldLoadAvatar) { - when (model.type) { - ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(model.name)) { - val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() - .setOldController(holder.binding.dialogAvatar.controller) - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - user.baseUrl, - model.name, - true - ), - user - ) - ) - .build() - holder.binding.dialogAvatar.controller = draweeController - } else { - holder.binding.dialogAvatar.visibility = View.GONE - } - ConversationType.ROOM_GROUP_CALL -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - holder.binding.dialogAvatar.setImageDrawable( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar( - holder.binding.dialogAvatar, - R.drawable.ic_avatar_group - ) - ) - ) - } else { - holder.binding.dialogAvatar.setImageDrawable( - ContextCompat.getDrawable(context, R.drawable.ic_circular_group) - ) - } - ConversationType.ROOM_PUBLIC_CALL -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - holder.binding.dialogAvatar.setImageDrawable( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar( - holder.binding.dialogAvatar, - R.drawable.ic_avatar_link - ) - ) - ) - } else { - holder.binding.dialogAvatar.setImageDrawable( - ContextCompat.getDrawable(context, R.drawable.ic_circular_link) - ) - } - else -> holder.binding.dialogAvatar.visibility = View.GONE + viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble) } + } else { + holder.binding.dialogUnreadBubble.chipBackgroundColor = lightBubbleFillColor + holder.binding.dialogUnreadBubble.setTextColor(lightBubbleTextColor) } } @@ -363,6 +341,9 @@ class ConversationItem( companion object { const val VIEW_TYPE = R.layout.rv_item_conversation_with_last_message + private const val MILLIES = 1000L private const val STATUS_SIZE_IN_DP = 9f + private const val UNREAD_BUBBLE_STROKE_WIDTH = 6.0f + private const val UNREAD_MESSAGES_TRESHOLD = 1000 } } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/MentionAutocompleteItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/MentionAutocompleteItem.java index 1773e1c60..13c655ff7 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/MentionAutocompleteItem.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/MentionAutocompleteItem.java @@ -29,15 +29,13 @@ import android.content.Context; import android.os.Build; import android.view.View; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.interfaces.DraweeController; import com.nextcloud.talk.R; import com.nextcloud.talk.data.user.model.User; +import com.nextcloud.talk.extensions.ImageViewExtensionsKt; import com.nextcloud.talk.models.json.mention.Mention; import com.nextcloud.talk.models.json.status.StatusType; import com.nextcloud.talk.ui.StatusDrawable; import com.nextcloud.talk.ui.theme.ViewThemeUtils; -import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.DisplayUtils; import java.util.List; @@ -151,34 +149,22 @@ public class MentionAutocompleteItem extends AbstractFlexibleItem= Build.VERSION_CODES.O) { - holder.binding.avatarDraweeView.getHierarchy().setPlaceholderImage( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar(holder.binding.avatarDraweeView, - R.drawable.ic_avatar_group))); + ImageViewExtensionsKt.loadAvatar( + holder.binding.avatarView, + viewThemeUtils.talk.themePlaceholderAvatar( + holder.binding.avatarView, + R.drawable.ic_avatar_group + ) + ); } else { - holder.binding.avatarDraweeView.setImageResource(R.drawable.ic_circular_group); + ImageViewExtensionsKt.loadAvatar(holder.binding.avatarView, R.drawable.ic_circular_group); } } else { String avatarId = objectId; - String avatarUrl = ApiUtils.getUrlForAvatar(currentUser.getBaseUrl(), - avatarId, true); - if (SOURCE_GUESTS.equals(source)) { avatarId = displayName; - avatarUrl = ApiUtils.getUrlForGuestAvatar( - currentUser.getBaseUrl(), - avatarId, - false); } - - holder.binding.avatarDraweeView.setController(null); - - DraweeController draweeController = Fresco.newDraweeControllerBuilder() - .setOldController(holder.binding.avatarDraweeView.getController()) - .setAutoPlayAnimations(true) - .setImageRequest(DisplayUtils.getImageRequestForUrl(avatarUrl)) - .build(); - holder.binding.avatarDraweeView.setController(draweeController); + ImageViewExtensionsKt.loadAvatar(holder.binding.avatarView, currentUser, avatarId, true); } drawStatus(holder); diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/MessageResultItem.kt b/app/src/main/java/com/nextcloud/talk/adapters/items/MessageResultItem.kt index d00df59d4..fe2424125 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/MessageResultItem.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/MessageResultItem.kt @@ -2,6 +2,8 @@ * Nextcloud Talk application * * @author Álvaro Brey + * @author Tim Krüger + * Copyright (C) 2022 Tim Krüger * Copyright (C) 2022 Álvaro Brey * Copyright (C) 2022 Nextcloud GmbH * @@ -27,9 +29,9 @@ import androidx.recyclerview.widget.RecyclerView import com.nextcloud.talk.R import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.RvItemSearchMessageBinding +import com.nextcloud.talk.extensions.loadThumbnail import com.nextcloud.talk.models.domain.SearchMessageEntry import com.nextcloud.talk.ui.theme.ViewThemeUtils -import com.nextcloud.talk.utils.DisplayUtils import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.IFilterable @@ -72,7 +74,7 @@ data class MessageResultItem constructor( ) { holder.binding.conversationTitle.text = messageEntry.title bindMessageExcerpt(holder) - loadImage(holder) + messageEntry.thumbnailURL?.let { holder.binding.thumbnail.loadThumbnail(it, currentUser) } } private fun bindMessageExcerpt(holder: ViewHolder) { @@ -83,17 +85,6 @@ data class MessageResultItem constructor( ) } - private fun loadImage(holder: ViewHolder) { - DisplayUtils.loadAvatarPlaceholder(holder.binding.thumbnail) - if (messageEntry.thumbnailURL != null) { - val imageRequest = DisplayUtils.getImageRequestForUrl( - messageEntry.thumbnailURL, - currentUser - ) - DisplayUtils.loadImage(holder.binding.thumbnail, imageRequest) - } - } - override fun filter(constraint: String?): Boolean = true override fun getItemViewType(): Int { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/ParticipantItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/ParticipantItem.java index ea676457b..6bc572d68 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/ParticipantItem.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/ParticipantItem.java @@ -27,23 +27,20 @@ package com.nextcloud.talk.adapters.items; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; -import android.os.Build; import android.text.TextUtils; import android.view.View; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.interfaces.DraweeController; import com.nextcloud.talk.R; import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.databinding.RvItemConversationInfoParticipantBinding; +import com.nextcloud.talk.extensions.ImageViewExtensionsKt; import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter; import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.models.json.participants.Participant.InCallFlags; import com.nextcloud.talk.models.json.status.StatusType; import com.nextcloud.talk.ui.StatusDrawable; import com.nextcloud.talk.ui.theme.ViewThemeUtils; -import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.DisplayUtils; import java.util.List; @@ -111,24 +108,22 @@ public class ParticipantItem extends AbstractFlexibleItem= Build.VERSION_CODES.O) { - holder.binding.avatarDraweeView.getHierarchy().setPlaceholderImage( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar(holder.binding.avatarDraweeView, - R.drawable.ic_avatar_group))); - } else { - holder.binding.avatarDraweeView.setImageResource(R.drawable.ic_circular_group); - } + ImageViewExtensionsKt.loadGroupCallAvatar(holder.binding.avatarView, viewThemeUtils); } else if (participant.getCalculatedActorType() == Participant.ActorType.EMAILS) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - holder.binding.avatarDraweeView.getHierarchy().setPlaceholderImage( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar(holder.binding.avatarDraweeView, - R.drawable.ic_avatar_mail))); - } else { - holder.binding.avatarDraweeView.setImageResource(R.drawable.ic_circular_mail); - } + ImageViewExtensionsKt.loadMailAvatar(holder.binding.avatarView, viewThemeUtils); } else if (participant.getCalculatedActorType() == Participant.ActorType.GUESTS || participant.getType() == Participant.ParticipantType.GUEST || participant.getType() == Participant.ParticipantType.GUEST_MODERATOR) { @@ -180,25 +161,11 @@ public class ParticipantItem extends AbstractFlexibleItem= Build.VERSION_CODES.O) { - val layers = arrayOfNulls(2) - layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background) - layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground) - val layerDrawable = LayerDrawable(layers) - binding.messageUserAvatar.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable)) - } else { - binding.messageUserAvatar.setImageResource(R.mipmap.ic_launcher) - } + binding.messageUserAvatar.loadChangelogBotAvatar() } else if (message.actorType == "bots") { - val drawable = TextDrawable.builder() - .beginConfig() - .bold() - .endConfig() - .buildRound( - ">", - ResourcesCompat.getColor(context.resources, R.color.black, null) - ) - binding.messageUserAvatar.visibility = View.VISIBLE - binding.messageUserAvatar.setImageDrawable(drawable) + binding.messageUserAvatar.loadBotsAvatar() } } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt index 8da197c1d..65a4bc8f6 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt @@ -29,8 +29,6 @@ package com.nextcloud.talk.adapters.messages import android.annotation.SuppressLint import android.content.Context import android.content.Intent -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable import android.net.Uri import android.text.TextUtils import android.util.Log @@ -40,18 +38,17 @@ import android.view.View import android.webkit.WebView import android.webkit.WebViewClient import android.widget.Toast -import androidx.appcompat.content.res.AppCompatResources import autodagger.AutoInjector import coil.load -import com.amulyakhare.textdrawable.TextDrawable import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.databinding.ItemCustomIncomingLocationMessageBinding +import com.nextcloud.talk.extensions.loadBotsAvatar +import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.UriUtils import com.nextcloud.talk.utils.preferences.AppPreferences import com.stfalcon.chatkit.messages.MessageHolders @@ -136,22 +133,9 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : Mess if (message.actorType == "guests") { // do nothing, avatar is set } else if (message.actorType == "bots" && message.actorId == "changelog") { - val layers = arrayOfNulls(2) - layers[0] = AppCompatResources.getDrawable(context!!, R.drawable.ic_launcher_background) - layers[1] = AppCompatResources.getDrawable(context!!, R.drawable.ic_launcher_foreground) - val layerDrawable = LayerDrawable(layers) - binding.messageUserAvatar.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable)) + binding.messageUserAvatar.loadChangelogBotAvatar() } else if (message.actorType == "bots") { - val drawable = TextDrawable.builder() - .beginConfig() - .bold() - .endConfig() - .buildRound( - ">", - context!!.resources.getColor(R.color.black) - ) - binding.messageUserAvatar.visibility = View.VISIBLE - binding.messageUserAvatar.setImageDrawable(drawable) + binding.messageUserAvatar.loadBotsAvatar() } } else { if (message.isOneToOneConversation) { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt index 5466c5a8e..c1b9a00ff 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt @@ -22,27 +22,23 @@ package com.nextcloud.talk.adapters.messages import android.annotation.SuppressLint import android.content.Context -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable -import android.os.Build import android.text.TextUtils import android.view.View import androidx.core.content.ContextCompat -import androidx.core.content.res.ResourcesCompat import autodagger.AutoInjector import coil.load -import com.amulyakhare.textdrawable.TextDrawable import com.nextcloud.talk.R import com.nextcloud.talk.activities.MainActivity import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.databinding.ItemCustomIncomingPollMessageBinding +import com.nextcloud.talk.extensions.loadBotsAvatar +import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.polls.ui.PollMainDialogFragment import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.preferences.AppPreferences import com.stfalcon.chatkit.messages.MessageHolders import javax.inject.Inject @@ -170,26 +166,9 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : MessageH if (message.actorType == "guests") { // do nothing, avatar is set } else if (message.actorType == "bots" && message.actorId == "changelog") { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val layers = arrayOfNulls(2) - layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background) - layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground) - val layerDrawable = LayerDrawable(layers) - binding.messageUserAvatar.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable)) - } else { - binding.messageUserAvatar.setImageResource(R.mipmap.ic_launcher) - } + binding.messageUserAvatar.loadChangelogBotAvatar() } else if (message.actorType == "bots") { - val drawable = TextDrawable.builder() - .beginConfig() - .bold() - .endConfig() - .buildRound( - ">", - ResourcesCompat.getColor(context.resources, R.color.black, null) - ) - binding.messageUserAvatar.visibility = View.VISIBLE - binding.messageUserAvatar.setImageDrawable(drawable) + binding.messageUserAvatar.loadBotsAvatar() } } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java index a60f089f1..78fbac71e 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java @@ -3,7 +3,7 @@ * * @author Andy Scherzinger * @author Tim Krüger - * Copyright (C) 2021 Tim Krüger + * Copyright (C) 2021-2022 Tim Krüger * Copyright (C) 2021 Andy Scherzinger * * This program is free software: you can redistribute it and/or modify @@ -23,9 +23,9 @@ package com.nextcloud.talk.adapters.messages; import android.view.View; +import android.widget.ImageView; import android.widget.ProgressBar; -import com.facebook.drawee.view.SimpleDraweeView; import com.google.android.material.card.MaterialCardView; import com.nextcloud.talk.R; import com.nextcloud.talk.databinding.ItemCustomIncomingPreviewMessageBinding; @@ -74,7 +74,7 @@ public class IncomingPreviewMessageViewHolder extends PreviewMessageViewHolder { } @Override - public SimpleDraweeView getPreviewContactPhoto() { + public ImageView getPreviewContactPhoto() { return binding.contactPhoto; } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt index 81b26b99a..800375c6a 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt @@ -26,24 +26,21 @@ package com.nextcloud.talk.adapters.messages import android.content.Context import android.content.Intent -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable import android.net.Uri -import android.os.Build import android.text.Spannable import android.text.SpannableString import android.text.TextUtils import android.util.TypedValue import android.view.View import androidx.core.content.ContextCompat -import androidx.core.content.res.ResourcesCompat import autodagger.AutoInjector import coil.load -import com.amulyakhare.textdrawable.TextDrawable import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding +import com.nextcloud.talk.extensions.loadBotsAvatar +import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback import com.nextcloud.talk.ui.theme.ViewThemeUtils @@ -179,28 +176,9 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : MessageHolde if (message.actorType == "guests") { // do nothing, avatar is set } else if (message.actorType == "bots" && message.actorId == "changelog") { - if (context != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val layers = arrayOfNulls(2) - layers[0] = ContextCompat.getDrawable(context!!, R.drawable.ic_launcher_background) - layers[1] = ContextCompat.getDrawable(context!!, R.drawable.ic_launcher_foreground) - val layerDrawable = LayerDrawable(layers) - binding.messageUserAvatar.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable)) - } else { - binding.messageUserAvatar.setImageResource(R.mipmap.ic_launcher) - } - } + binding.messageUserAvatar.loadChangelogBotAvatar() } else if (message.actorType == "bots") { - val drawable = TextDrawable.builder() - .beginConfig() - .bold() - .endConfig() - .buildRound( - ">", - ResourcesCompat.getColor(context!!.resources, R.color.black, null) - ) - binding.messageUserAvatar.visibility = View.VISIBLE - binding.messageUserAvatar.setImageDrawable(drawable) + binding.messageUserAvatar.loadBotsAvatar() } } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt index 2cc1419aa..ab67e99d6 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt @@ -28,9 +28,6 @@ package com.nextcloud.talk.adapters.messages import android.annotation.SuppressLint import android.content.Context -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable -import android.os.Build import android.text.TextUtils import android.util.Log import android.view.View @@ -46,10 +43,10 @@ import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding +import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.preferences.AppPreferences import com.stfalcon.chatkit.messages.MessageHolders import java.util.concurrent.ExecutionException @@ -245,15 +242,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message if (message.actorType == "guests") { // do nothing, avatar is set } else if (message.actorType == "bots" && message.actorId == "changelog") { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val layers = arrayOfNulls(2) - layers[0] = ContextCompat.getDrawable(context!!, R.drawable.ic_launcher_background) - layers[1] = ContextCompat.getDrawable(context!!, R.drawable.ic_launcher_foreground) - val layerDrawable = LayerDrawable(layers) - binding.messageUserAvatar.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable)) - } else { - binding.messageUserAvatar.setImageResource(R.mipmap.ic_launcher) - } + binding.messageUserAvatar.loadChangelogBotAvatar() } else if (message.actorType == "bots") { val drawable = TextDrawable.builder() .beginConfig() diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt index 5a80b08ef..f3da8ee2d 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt @@ -25,14 +25,12 @@ import android.content.Intent import android.net.Uri import android.util.Log import android.view.View -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.interfaces.DraweeController +import coil.load import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.databinding.ReferenceInsideMessageBinding import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.opengraph.OpenGraphOverall import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable @@ -92,12 +90,7 @@ class LinkPreview { val referenceThumbUrl = reference.openGraphObject?.thumb if (!referenceThumbUrl.isNullOrEmpty()) { binding.referenceThumbImage.visibility = View.VISIBLE - val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() - .setAutoPlayAnimations(true) - .setImageRequest(DisplayUtils.getImageRequestForUrl(referenceThumbUrl)) - .build() - binding.referenceThumbImage.controller = - draweeController + binding.referenceThumbImage.load(referenceThumbUrl) } else { binding.referenceThumbImage.visibility = View.GONE } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java index f6860c9da..82778e9dd 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java @@ -23,9 +23,9 @@ package com.nextcloud.talk.adapters.messages; import android.view.View; +import android.widget.ImageView; import android.widget.ProgressBar; -import com.facebook.drawee.view.SimpleDraweeView; import com.google.android.material.card.MaterialCardView; import com.nextcloud.talk.R; import com.nextcloud.talk.databinding.ItemCustomOutcomingPreviewMessageBinding; @@ -75,7 +75,7 @@ public class OutcomingPreviewMessageViewHolder extends PreviewMessageViewHolder } @Override - public SimpleDraweeView getPreviewContactPhoto() { + public ImageView getPreviewContactPhoto() { return binding.contactPhoto; } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageViewHolder.kt index 7891e601b..cfd4be17b 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageViewHolder.kt @@ -5,7 +5,7 @@ * @author Marcel Hibbe * @author Andy Scherzinger * @author Tim Krüger - * Copyright (C) 2021 Tim Krüger + * Copyright (C) 2021-2022 Tim Krüger * Copyright (C) 2021 Andy Scherzinger * Copyright (C) 2021 Marcel Hibbe * Copyright (C) 2017-2018 Mario Danic @@ -30,7 +30,6 @@ import android.content.Context import android.content.Intent import android.graphics.PorterDuff import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable import android.net.Uri import android.os.Handler import android.util.Base64 @@ -38,13 +37,13 @@ import android.util.Log import android.view.Gravity import android.view.MenuItem import android.view.View +import android.widget.ImageView import android.widget.PopupMenu import android.widget.ProgressBar import androidx.appcompat.view.ContextThemeWrapper import androidx.core.content.ContextCompat import androidx.emoji.widget.EmojiTextView import autodagger.AutoInjector -import com.facebook.drawee.view.SimpleDraweeView import com.google.android.material.card.MaterialCardView import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication @@ -53,6 +52,7 @@ import com.nextcloud.talk.components.filebrowser.models.BrowserFile import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding +import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback import com.nextcloud.talk.ui.theme.ViewThemeUtils @@ -92,6 +92,8 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : lateinit var commonMessageInterface: CommonMessageInterface var previewMessageInterface: PreviewMessageInterface? = null + private var placeholder: Drawable? = null + init { sharedApplication!!.componentApplication.inject(this) } @@ -118,13 +120,7 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : } } if (ACTOR_TYPE_BOTS == message.actorType && ACTOR_ID_CHANGELOG == message.actorId) { - if (context != null) { - val layers = arrayOfNulls(2) - layers[0] = ContextCompat.getDrawable(context!!, R.drawable.ic_launcher_background) - layers[1] = ContextCompat.getDrawable(context!!, R.drawable.ic_launcher_foreground) - val layerDrawable = LayerDrawable(layers) - userAvatar.hierarchy.setPlaceholderImage(DisplayUtils.getRoundedDrawable(layerDrawable)) - } + userAvatar.loadChangelogBotAvatar() } } } @@ -150,11 +146,10 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : } if (message.selectedIndividualHashMap!!.containsKey(KEY_CONTACT_PHOTO)) { image = previewContactPhoto - val drawable = getDrawableFromContactDetails( + placeholder = getDrawableFromContactDetails( context, message.selectedIndividualHashMap!![KEY_CONTACT_PHOTO] ) - image.hierarchy.setPlaceholderImage(drawable) } else if (message.selectedIndividualHashMap!!.containsKey(KEY_MIMETYPE)) { val mimetype = message.selectedIndividualHashMap!![KEY_MIMETYPE] val drawableResourceId = getDrawableResourceIdForMimeType(mimetype) @@ -170,7 +165,7 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : PorterDuff.Mode.SRC_ATOP ) } - image.hierarchy.setPlaceholderImage(drawable) + placeholder = drawable } else { fetchFileInformation( "/" + message.selectedIndividualHashMap!![KEY_PATH], @@ -208,13 +203,13 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : DisplayUtils.setClickableString("Tenor", "https://tenor.com", messageText) } else { if (message.messageType == ChatMessage.MessageType.SINGLE_LINK_IMAGE_MESSAGE.name) { - (clickView as SimpleDraweeView?)?.setOnClickListener { + clickView!!.setOnClickListener { val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(message.imageUrl)) browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context!!.startActivity(browserIntent) } } else { - (clickView as SimpleDraweeView?)?.setOnClickListener(null) + clickView!!.setOnClickListener(null) } messageText.text = "" } @@ -238,6 +233,10 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : commonMessageInterface.onClickReaction(chatMessage, emoji) } + override fun getPayloadForImageLoader(message: ChatMessage?): Any? { + return placeholder + } + private fun getDrawableFromContactDetails(context: Context?, base64: String?): Drawable? { var drawable: Drawable? = null if (base64 != "") { @@ -300,8 +299,7 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : if (browserFileList.isNotEmpty()) { Handler(context!!.mainLooper).post { val resourceId = getDrawableResourceIdForMimeType(browserFileList[0].mimeType) - val drawable = ContextCompat.getDrawable(context!!, resourceId) - image.hierarchy.setPlaceholderImage(drawable) + placeholder = ContextCompat.getDrawable(context!!, resourceId) } } } @@ -324,7 +322,7 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : abstract val messageText: EmojiTextView abstract val previewContainer: View abstract val previewContactContainer: MaterialCardView - abstract val previewContactPhoto: SimpleDraweeView + abstract val previewContactPhoto: ImageView abstract val previewContactName: EmojiTextView abstract val previewContactProgressBar: ProgressBar? diff --git a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt index 5407a12f4..cbad9f041 100644 --- a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt +++ b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt @@ -4,6 +4,8 @@ * @author Marcel Hibbe * @author Andy Scherzinger * @author Mario Danic + * @author Tim Krüger + * Copyright (C) 2022 Tim Krüger * Copyright (C) 2022 Marcel Hibbe * Copyright (C) 2022 Andy Scherzinger * Copyright (C) 2017 Mario Danic @@ -46,9 +48,7 @@ import coil.decode.GifDecoder import coil.decode.ImageDecoderDecoder import coil.decode.SvgDecoder import coil.memory.MemoryCache -import com.facebook.cache.disk.DiskCacheConfig -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.imagepipeline.core.ImagePipelineConfig +import coil.util.DebugLogger import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.components.filebrowser.webdav.DavUtils import com.nextcloud.talk.dagger.modules.BusModule @@ -66,7 +66,6 @@ import com.nextcloud.talk.utils.ClosedInterfaceImpl import com.nextcloud.talk.utils.DeviceUtils import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.NotificationUtils -import com.nextcloud.talk.utils.OkHttpNetworkFetcherWithCache import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageModule import com.nextcloud.talk.utils.database.user.UserModule import com.nextcloud.talk.utils.preferences.AppPreferences @@ -174,18 +173,6 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver { setAppTheme(appPreferences.theme) super.onCreate() - val imagePipelineConfig = ImagePipelineConfig.newBuilder(this) - .setNetworkFetcher(OkHttpNetworkFetcherWithCache(okHttpClient)) - .setMainDiskCacheConfig( - DiskCacheConfig.newBuilder(this) - .setMaxCacheSize(0) - .setMaxCacheSizeOnLowDiskSpace(0) - .setMaxCacheSizeOnVeryLowDiskSpace(0) - .build() - ) - .build() - - Fresco.initialize(this, imagePipelineConfig) Security.insertProviderAt(Conscrypt.newProvider(), 1) ClosedInterfaceImpl().providerInstallerInstallIfNeededAsync() @@ -240,7 +227,7 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver { } private fun buildDefaultImageLoader(): ImageLoader { - return ImageLoader.Builder(applicationContext) + val imageLoaderBuilder = ImageLoader.Builder(applicationContext) .memoryCache { // Use 50% of the application's available memory. MemoryCache.Builder(applicationContext).maxSizePercent(FIFTY_PERCENT).build() @@ -254,8 +241,12 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver { } add(SvgDecoder.Factory()) } - .okHttpClient(okHttpClient) - .build() + + if (BuildConfig.DEBUG) { + imageLoaderBuilder.logger(DebugLogger()) + } + + return imageLoaderBuilder.build() } companion object { diff --git a/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java b/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java index eeeaa45e0..07e733e26 100644 --- a/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java +++ b/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java @@ -27,7 +27,7 @@ import android.text.Editable; import android.text.Spanned; import android.widget.EditText; -import com.facebook.widget.text.span.BetterImageSpan; +import third.parties.fresco.BetterImageSpan; import com.nextcloud.talk.R; import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.models.json.mention.Mention; diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index a94d7ca03..1f0b04a1a 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -5,7 +5,7 @@ * @author Marcel Hibbe * @author Andy Scherzinger * @author Tim Krüger - * Copyright (C) 2021 Tim Krüger + * Copyright (C) 2021-2022 Tim Krüger * Copyright (C) 2021 Andy Scherzinger * Copyright (C) 2021-2022 Marcel Hibbe * Copyright (C) 2017-2019 Mario Danic @@ -37,8 +37,9 @@ import android.content.pm.PackageManager import android.content.res.AssetFileDescriptor import android.content.res.Resources import android.database.Cursor -import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable import android.media.MediaPlayer import android.media.MediaRecorder import android.net.Uri @@ -78,7 +79,7 @@ import androidx.appcompat.view.ContextThemeWrapper import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.core.content.PermissionChecker -import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import androidx.core.graphics.drawable.toBitmap import androidx.core.widget.doAfterTextChanged import androidx.emoji.text.EmojiCompat import androidx.emoji.widget.EmojiTextView @@ -90,15 +91,13 @@ import androidx.work.OneTimeWorkRequest import androidx.work.WorkInfo import androidx.work.WorkManager import autodagger.AutoInjector +import coil.imageLoader import coil.load +import coil.request.ImageRequest +import coil.target.Target +import coil.transform.CircleCropTransformation import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler -import com.facebook.common.executors.UiThreadImmediateExecutorService -import com.facebook.common.references.CloseableReference -import com.facebook.datasource.DataSource -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber -import com.facebook.imagepipeline.image.CloseableImage import com.google.android.flexbox.FlexboxLayout import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.nextcloud.talk.BuildConfig @@ -134,6 +133,7 @@ import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.ControllerChatBinding import com.nextcloud.talk.events.UserMentionClickEvent import com.nextcloud.talk.events.WebSocketCommunicationEvent +import com.nextcloud.talk.extensions.loadAvatarOrImagePreview import com.nextcloud.talk.jobs.DownloadFileToCacheWorker import com.nextcloud.talk.jobs.ShareOperationWorker import com.nextcloud.talk.jobs.UploadAndShareFilesWorker @@ -165,7 +165,6 @@ import com.nextcloud.talk.utils.ConductorRemapping import com.nextcloud.talk.utils.ConductorRemapping.remapChatController import com.nextcloud.talk.utils.ContactUtils import com.nextcloud.talk.utils.DateUtils -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.FileUtils import com.nextcloud.talk.utils.ImageEmojiEditText import com.nextcloud.talk.utils.MagicCharPolicy @@ -462,42 +461,48 @@ class ChatController(args: Bundle) : private fun loadAvatarForStatusBar() { if (isOneToOneConversation() && activity != null) { - val imageRequest = DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - conversationUser?.baseUrl, - currentConversation?.name, - true - ), - conversationUser!! + + val url = ApiUtils.getUrlForAvatar( + conversationUser!!.baseUrl, + currentConversation!!.name, + true ) + val target = object : Target { - val imagePipeline = Fresco.getImagePipeline() - val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null) + private fun setIcon(drawable: Drawable?) { - dataSource.subscribe( - object : BaseBitmapDataSubscriber() { - override fun onNewResultImpl(bitmap: Bitmap?) { - if (actionBar != null && bitmap != null && resources != null) { - val avatarSize = (actionBar?.height!! / TOOLBAR_AVATAR_RATIO).roundToInt() - if (avatarSize > 0) { - val bitmapResized = Bitmap.createScaledBitmap(bitmap, avatarSize, avatarSize, false) + actionBar?.let { + val avatarSize = (it.height / TOOLBAR_AVATAR_RATIO).roundToInt() - val roundedBitmapDrawable = - RoundedBitmapDrawableFactory.create(resources!!, bitmapResized) - roundedBitmapDrawable.isCircular = true - roundedBitmapDrawable.setAntiAlias(true) - actionBar?.setIcon(roundedBitmapDrawable) - } else { - Log.d(TAG, "loadAvatarForStatusBar avatarSize <= 0") - } + if (drawable != null && avatarSize > 0) { + val bitmap = drawable.toBitmap(avatarSize, avatarSize) + it.setIcon(BitmapDrawable(resources, bitmap)) + } else { + Log.d(TAG, "loadAvatarForStatusBar avatarSize <= 0") } } + } - override fun onFailureImpl(dataSource: DataSource>) { - // unused atm - } - }, - UiThreadImmediateExecutorService.getInstance() + override fun onStart(placeholder: Drawable?) { + this.setIcon(placeholder) + } + + override fun onSuccess(result: Drawable) { + this.setIcon(result) + } + } + + val credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token) + + context.imageLoader.enqueue( + ImageRequest.Builder(context) + .data(url) + .addHeader("Authorization", credentials) + .placeholder(R.drawable.ic_user) + .transformations(CircleCropTransformation()) + .crossfade(true) + .target(target) + .build() ) } } @@ -617,14 +622,8 @@ class ChatController(args: Bundle) : adapter = TalkMessagesListAdapter( senderId, messageHolders, - ImageLoader { imageView, url, payload -> - val draweeController = Fresco.newDraweeControllerBuilder() - .setImageRequest(DisplayUtils.getImageRequestForUrl(url, conversationUser)) - .setControllerListener(DisplayUtils.getImageControllerListener(imageView)) - .setOldController(imageView.controller) - .setAutoPlayAnimations(true) - .build() - imageView.controller = draweeController + ImageLoader { imageView, url, _ -> + imageView.loadAvatarOrImagePreview(url!!, conversationUser, placeholder = payload as? Drawable) }, this ) diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt index 84930b8ee..a44f16f4b 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt @@ -28,9 +28,6 @@ package com.nextcloud.talk.controllers import android.annotation.SuppressLint import android.content.Intent -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable -import android.os.Build import android.os.Bundle import android.os.Parcelable import android.text.TextUtils @@ -42,7 +39,6 @@ import android.view.View.VISIBLE import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.SwitchCompat -import androidx.core.content.ContextCompat import androidx.work.Data import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager @@ -53,7 +49,6 @@ import com.afollestad.materialdialogs.bottomsheets.BottomSheet import com.afollestad.materialdialogs.datetime.dateTimePicker import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler -import com.facebook.drawee.backends.pipeline.Fresco import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.nextcloud.talk.R import com.nextcloud.talk.adapters.items.ParticipantItem @@ -67,6 +62,10 @@ import com.nextcloud.talk.conversation.info.GuestAccessHelper import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.ControllerConversationInfoBinding import com.nextcloud.talk.events.EventStatus +import com.nextcloud.talk.extensions.loadAvatar +import com.nextcloud.talk.extensions.loadSystemAvatar +import com.nextcloud.talk.extensions.loadGroupCallAvatar +import com.nextcloud.talk.extensions.loadPublicCallAvatar import com.nextcloud.talk.jobs.DeleteConversationWorker import com.nextcloud.talk.jobs.LeaveConversationWorker import com.nextcloud.talk.models.json.conversations.Conversation @@ -83,7 +82,6 @@ import com.nextcloud.talk.shareditems.activities.SharedItemsActivity import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DateConstants import com.nextcloud.talk.utils.DateUtils -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule @@ -778,54 +776,16 @@ class ConversationInfoController(args: Bundle) : private fun loadConversationAvatar() { when (conversation!!.type) { Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(conversation!!.name)) { - val draweeController = Fresco.newDraweeControllerBuilder() - .setOldController(binding.avatarImage.controller) - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - conversationUser!!.baseUrl, - conversation!!.name, - true - ), - conversationUser - ) - ) - .build() - binding.avatarImage.controller = draweeController + conversation!!.name?.let { binding.avatarImage.loadAvatar(conversationUser!!, it) } } Conversation.ConversationType.ROOM_GROUP_CALL -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - binding.avatarImage.hierarchy.setPlaceholderImage( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar(binding.avatarImage, R.drawable.ic_avatar_group) - ) - ) - } else { - binding.avatarImage.hierarchy.setPlaceholderImage( - R.drawable.ic_circular_group - ) - } + binding.avatarImage.loadGroupCallAvatar(viewThemeUtils) } Conversation.ConversationType.ROOM_PUBLIC_CALL -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - binding.avatarImage.hierarchy.setPlaceholderImage( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar(binding.avatarImage, R.drawable.ic_avatar_link) - ) - ) - } else { - binding.avatarImage.hierarchy.setPlaceholderImage( - R.drawable.ic_circular_link - ) - } + binding.avatarImage.loadPublicCallAvatar(viewThemeUtils) } Conversation.ConversationType.ROOM_SYSTEM -> { - val layers = arrayOfNulls(2) - layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background) - layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground) - val layerDrawable = LayerDrawable(layers) - binding.avatarImage.hierarchy.setPlaceholderImage(DisplayUtils.getRoundedDrawable(layerDrawable)) + binding.avatarImage.loadSystemAvatar() } else -> { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt index 8df593a46..4d6fafc18 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt @@ -31,7 +31,7 @@ import android.app.SearchManager import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.graphics.Bitmap +import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build import android.os.Bundle @@ -49,8 +49,6 @@ import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.SearchView -import androidx.core.content.res.ResourcesCompat -import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.view.MenuItemCompat import androidx.fragment.app.DialogFragment import androidx.recyclerview.widget.RecyclerView @@ -58,15 +56,13 @@ import androidx.work.Data import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import autodagger.AutoInjector +import coil.imageLoader +import coil.request.ImageRequest +import coil.target.Target +import coil.transform.CircleCropTransformation import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler -import com.facebook.common.executors.UiThreadImmediateExecutorService -import com.facebook.common.references.CloseableReference -import com.facebook.datasource.DataSource -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber -import com.facebook.imagepipeline.image.CloseableImage import com.google.android.material.button.MaterialButton import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.nextcloud.talk.R @@ -104,7 +100,6 @@ import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ClosedInterfaceImpl import com.nextcloud.talk.utils.ConductorRemapping.remapChatController -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.FileUtils import com.nextcloud.talk.utils.Mimetype import com.nextcloud.talk.utils.ParticipantPermissions @@ -211,78 +206,56 @@ class ConversationsListController(bundle: Bundle) : prepareViews() } - private fun loadUserAvatar(button: MaterialButton) { - if (activity != null) { - val imageRequest = DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - currentUser!!.baseUrl, - currentUser!!.userId, - true - ), - currentUser - ) - val imagePipeline = Fresco.getImagePipeline() - val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null) - dataSource.subscribe( - object : BaseBitmapDataSubscriber() { - override fun onNewResultImpl(bitmap: Bitmap?) { - if (bitmap != null && resources != null) { - val roundedBitmapDrawable = RoundedBitmapDrawableFactory.create( - resources!!, - bitmap - ) - roundedBitmapDrawable.isCircular = true - roundedBitmapDrawable.setAntiAlias(true) - button.icon = roundedBitmapDrawable - } - } + private fun loadUserAvatar( + target: Target + ) { - override fun onFailureImpl(dataSource: DataSource>) { - if (resources != null) { - button.icon = ResourcesCompat.getDrawable(resources!!, R.drawable.ic_user, null) - } - } - }, - UiThreadImmediateExecutorService.getInstance() + if (activity != null) { + val url = ApiUtils.getUrlForAvatar( + currentUser!!.baseUrl, + currentUser!!.userId, + true + ) + + val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token) + + context.imageLoader.enqueue( + ImageRequest.Builder(context) + .data(url) + .addHeader("Authorization", credentials) + .placeholder(R.drawable.ic_user) + .transformations(CircleCropTransformation()) + .crossfade(true) + .target(target) + .build() ) } } - private fun loadUserAvatar(menuItem: MenuItem) { - if (activity != null) { - val imageRequest = DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - currentUser!!.baseUrl, - currentUser!!.userId, - true - ), - currentUser - ) - val imagePipeline = Fresco.getImagePipeline() - val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null) - dataSource.subscribe( - object : BaseBitmapDataSubscriber() { - override fun onNewResultImpl(bitmap: Bitmap?) { - if (bitmap != null && resources != null) { - val roundedBitmapDrawable = RoundedBitmapDrawableFactory.create( - resources!!, - bitmap - ) - roundedBitmapDrawable.isCircular = true - roundedBitmapDrawable.setAntiAlias(true) - menuItem.icon = roundedBitmapDrawable - } - } + private fun loadUserAvatar(button: MaterialButton) { - override fun onFailureImpl(dataSource: DataSource>) { - if (resources != null) { - menuItem.icon = ResourcesCompat.getDrawable(resources!!, R.drawable.ic_user, null) - } - } - }, - UiThreadImmediateExecutorService.getInstance() - ) + val target = object : Target { + override fun onStart(placeholder: Drawable?) { + button.icon = placeholder + } + override fun onSuccess(result: Drawable) { + button.icon = result + } } + + loadUserAvatar(target) + } + + private fun loadUserAvatar(menuItem: MenuItem) { + val target = object : Target { + override fun onStart(placeholder: Drawable?) { + menuItem.icon = placeholder + } + override fun onSuccess(result: Drawable) { + menuItem.icon = result + } + } + loadUserAvatar(target) } override fun onAttach(view: View) { diff --git a/app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt b/app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt new file mode 100644 index 000000000..c8b391d22 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt @@ -0,0 +1,283 @@ +/* + * Nextcloud Talk application + * + * @author Tim Krüger + * Copyright (C) 2022 Tim Krüger + * Copyright (C) 2022 Nextcloud GmbH + * + * 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 . + */ + +@file:Suppress("TooManyFunctions") + +package com.nextcloud.talk.extensions + +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import android.os.Build +import android.util.Log +import android.widget.ImageView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import coil.annotation.ExperimentalCoilApi +import coil.imageLoader +import coil.load +import coil.request.ImageRequest +import coil.request.SuccessResult +import coil.result +import coil.transform.CircleCropTransformation +import coil.transform.RoundedCornersTransformation +import com.amulyakhare.textdrawable.TextDrawable +import com.nextcloud.talk.R +import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.ui.theme.ViewThemeUtils +import com.nextcloud.talk.utils.ApiUtils + +private const val ROUNDING_PIXEL = 16f +private const val TAG = "ImageViewExtensions" + +fun ImageView.loadAvatar( + user: User, + avatar: String, + requestBigSize: Boolean = true +): io.reactivex.disposables +.Disposable { + + val imageRequestUri = ApiUtils.getUrlForAvatar( + user.baseUrl, + avatar, + requestBigSize + ) + + return loadAvatarInternal(user, imageRequestUri, false) +} + +fun ImageView.replaceAvatar( + user: User, + avatar: String, + requestBigSize: Boolean = true +): io.reactivex.disposables +.Disposable { + + val imageRequestUri = ApiUtils.getUrlForAvatar( + user.baseUrl, + avatar, + requestBigSize + ) + + return loadAvatarInternal(user, imageRequestUri, true) +} + +@OptIn(ExperimentalCoilApi::class) +private fun ImageView.loadAvatarInternal( + user: User?, + url: String, + replace: Boolean +): io.reactivex.disposables +.Disposable { + + if (replace && this.result is SuccessResult) { + val result = this.result as SuccessResult + val memoryCacheKey = result.memoryCacheKey + val memoryCache = context.imageLoader.memoryCache + memoryCacheKey?.let { memoryCache?.remove(it) } + + val diskCacheKey = result.diskCacheKey + val diskCache = context.imageLoader.diskCache + diskCacheKey?.let { diskCache?.remove(it) } + } + + return DisposableWrapper( + load(url) { + user?.let { + addHeader( + "Authorization", + ApiUtils.getCredentials(user.username, user.token) + ) + } + transformations(CircleCropTransformation()) + placeholder(R.drawable.account_circle_96dp) + listener(onError = { _, result -> + Log.w(TAG, "Can't load avatar with URL: $url", result.throwable) + }) + } + ) +} + +@Deprecated("Use function loadAvatar", level = DeprecationLevel.WARNING) +fun ImageView.loadAvatarWithUrl(user: User? = null, url: String): io.reactivex.disposables.Disposable { + return loadAvatarInternal(user, url, false) +} + +fun ImageView.loadThumbnail(url: String, user: User): io.reactivex.disposables.Disposable { + val requestBuilder = ImageRequest.Builder(context) + .data(url) + .crossfade(true) + .target(this) + .transformations(CircleCropTransformation()) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val layers = arrayOfNulls(2) + layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background) + layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground) + requestBuilder.placeholder(LayerDrawable(layers)) + } else { + requestBuilder.placeholder(R.mipmap.ic_launcher) + } + + if (url.startsWith(user.baseUrl!!) && + (url.contains("index.php/core/preview?fileId=") || url.contains("/avatar/")) + ) { + requestBuilder.addHeader( + "Authorization", + ApiUtils.getCredentials(user.username, user.token) + ) + } + + return DisposableWrapper(context.imageLoader.enqueue(requestBuilder.build())) +} + +fun ImageView.loadImage(url: String, user: User, placeholder: Drawable? = null): io.reactivex.disposables.Disposable { + + val requestBuilder = ImageRequest.Builder(context) + .data(url) + .crossfade(true) + .target(this) + .placeholder(placeholder) + .error(placeholder) + .transformations(RoundedCornersTransformation(ROUNDING_PIXEL, ROUNDING_PIXEL, ROUNDING_PIXEL, ROUNDING_PIXEL)) + + if (url.startsWith(user.baseUrl!!) && + (url.contains("index.php/core/preview?fileId=") || url.contains("/avatar/")) + ) { + requestBuilder.addHeader( + "Authorization", + ApiUtils.getCredentials(user.username, user.token) + ) + } + + return DisposableWrapper(context.imageLoader.enqueue(requestBuilder.build())) +} + +fun ImageView.loadAvatarOrImagePreview(url: String, user: User, placeholder: Drawable? = null): io.reactivex +.disposables.Disposable { + return if (url.contains("/avatar/")) { + loadAvatarInternal(user, url, false) + } else { + loadImage(url, user, placeholder) + } +} + +fun ImageView.loadAvatar(any: Any?): io.reactivex.disposables.Disposable { + return DisposableWrapper( + load(any) { + transformations(CircleCropTransformation()) + } + ) +} + +fun ImageView.loadSystemAvatar(): io.reactivex.disposables.Disposable { + val data: Any = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val layers = arrayOfNulls(2) + layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background) + layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground) + val layerDrawable = LayerDrawable(layers) + layerDrawable + } else { + R.mipmap.ic_launcher + } + + return DisposableWrapper( + load(data) { + transformations(CircleCropTransformation()) + } + ) +} + +fun ImageView.loadChangelogBotAvatar(): io.reactivex.disposables.Disposable { + return loadSystemAvatar() +} + +fun ImageView.loadBotsAvatar(): io.reactivex.disposables.Disposable { + return loadAvatar( + TextDrawable.builder() + .beginConfig() + .bold() + .endConfig() + .buildRound( + ">", + ResourcesCompat.getColor(context.resources, R.color.black, null) + ) + ) +} + +fun ImageView.loadGroupCallAvatar(viewThemeUtils: ViewThemeUtils): io.reactivex.disposables.Disposable { + + val data: Any = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + viewThemeUtils.talk.themePlaceholderAvatar(this, R.drawable.ic_avatar_group) as Any + } else { + R.drawable.ic_circular_group + } + return loadAvatar(data) +} + +fun ImageView.loadPublicCallAvatar(viewThemeUtils: ViewThemeUtils): io.reactivex.disposables.Disposable { + val data: Any = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + viewThemeUtils.talk.themePlaceholderAvatar(this, R.drawable.ic_avatar_link) as Any + } else { + R.drawable.ic_circular_link + } + return loadAvatar(data) +} + +fun ImageView.loadMailAvatar(viewThemeUtils: ViewThemeUtils): io.reactivex.disposables.Disposable { + val data: Any = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + viewThemeUtils.talk.themePlaceholderAvatar(this, R.drawable.ic_avatar_mail) as Any + } else { + R.drawable.ic_circular_mail + } + return loadAvatar(data) +} + +fun ImageView.loadGuestAvatar(user: User, name: String, big: Boolean): io.reactivex.disposables.Disposable { + return loadGuestAvatar(user.baseUrl!!, name, big) +} + +fun ImageView.loadGuestAvatar(baseUrl: String, name: String, big: Boolean): io.reactivex.disposables.Disposable { + val imageRequestUri = ApiUtils.getUrlForGuestAvatar( + baseUrl, + name, + big + ) + return DisposableWrapper( + load(imageRequestUri) { + transformations(CircleCropTransformation()) + listener(onError = { _, result -> + Log.w(TAG, "Can't load guest avatar with URL: $imageRequestUri", result.throwable) + }) + } + ) +} + +private class DisposableWrapper(private val disposable: coil.request.Disposable) : io.reactivex.disposables + .Disposable { + + override fun dispose() { + disposable.dispose() + } + + override fun isDisposed(): Boolean { + return disposable.isDisposed + } +} diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt index 2d7dc11b7..d70735032 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt @@ -526,7 +526,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor notificationUser.id, false ) else ApiUtils.getUrlForGuestAvatar(baseUrl, notificationUser.name, false) - person.setIcon(loadAvatarSync(avatarUrl)) + person.setIcon(loadAvatarSync(avatarUrl, context!!)) } notificationBuilder.setStyle(getStyle(person.build(), style)) } diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterViewHolder.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterViewHolder.kt index e69f0038b..bc415f4e2 100644 --- a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterViewHolder.kt @@ -22,16 +22,15 @@ package com.nextcloud.talk.polls.adapters import android.annotation.SuppressLint import android.text.TextUtils -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.interfaces.DraweeController +import android.widget.ImageView import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.PollResultVoterItemBinding +import com.nextcloud.talk.extensions.loadAvatar +import com.nextcloud.talk.extensions.loadGuestAvatar import com.nextcloud.talk.polls.model.PollDetails import com.nextcloud.talk.ui.theme.ViewThemeUtils -import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils class PollResultVoterViewHolder( private val user: User, @@ -46,45 +45,19 @@ class PollResultVoterViewHolder( binding.root.setOnClickListener { clickListener.onClick() } binding.pollVoterName.text = item.details.actorDisplayName - binding.pollVoterAvatar.controller = getAvatarDraweeController(item.details) + loadAvatar(item.details, binding.pollVoterAvatar) viewThemeUtils.dialog.colorDialogSupportingText(binding.pollVoterName) } - private fun getAvatarDraweeController(pollDetail: PollDetails): DraweeController? { - var draweeController: DraweeController? = null + private fun loadAvatar(pollDetail: PollDetails, avatar: ImageView) { if (pollDetail.actorType == "guests") { var displayName = NextcloudTalkApplication.sharedApplication?.resources?.getString(R.string.nc_guest) if (!TextUtils.isEmpty(pollDetail.actorDisplayName)) { displayName = pollDetail.actorDisplayName!! } - draweeController = Fresco.newDraweeControllerBuilder() - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForGuestAvatar( - user.baseUrl, - displayName, - false - ), - user - ) - ) - .build() + avatar.loadGuestAvatar(user, displayName!!, false) } else if (pollDetail.actorType == "users") { - draweeController = Fresco.newDraweeControllerBuilder() - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - user.baseUrl, - pollDetail.actorId, - false - ), - user - ) - ) - .build() + avatar.loadAvatar(user, pollDetail.actorId!!, false) } - return draweeController } } diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewViewHolder.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewViewHolder.kt index d071cc76b..d934115fb 100644 --- a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewViewHolder.kt @@ -2,6 +2,8 @@ * Nextcloud Talk application * * @author Marcel Hibbe + * @author Tim Krüger + * Copyright (C) 2022 Tim Krüger * Copyright (C) 2022 Marcel Hibbe * * This program is free software: you can redistribute it and/or modify @@ -22,20 +24,16 @@ package com.nextcloud.talk.polls.adapters import android.annotation.SuppressLint import android.text.TextUtils +import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView -import androidx.core.content.res.ResourcesCompat -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.generic.RoundingParams -import com.facebook.drawee.interfaces.DraweeController -import com.facebook.drawee.view.SimpleDraweeView import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.PollResultVotersOverviewItemBinding +import com.nextcloud.talk.extensions.loadAvatar +import com.nextcloud.talk.extensions.loadGuestAvatar import com.nextcloud.talk.polls.model.PollDetails -import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils class PollResultVotersOverviewViewHolder( private val user: User, @@ -61,24 +59,14 @@ class PollResultVotersOverviewViewHolder( for (i in 0 until avatarsToDisplay) { val pollDetails = item.detailsList[i] - val avatar = SimpleDraweeView(binding.root.context) + val avatar = ImageView(binding.root.context) layoutParams.marginStart = i * AVATAR_OFFSET avatar.layoutParams = layoutParams avatar.translationZ = i.toFloat() * -1 - val roundingParams = RoundingParams.fromCornersRadius(AVATAR_RADIUS) - roundingParams.roundAsCircle = true - roundingParams.borderColor = ResourcesCompat.getColor( - itemView.context.resources!!, - R.color.vote_dialog_background, - null - ) - roundingParams.borderWidth = DisplayUtils.convertDpToPixel(2.0f, itemView.context) - - avatar.hierarchy.roundingParams = roundingParams - avatar.controller = getAvatarDraweeController(pollDetails) + loadAvatar(pollDetails, avatar) binding.votersAvatarsOverviewWrapper.addView(avatar) @@ -92,47 +80,20 @@ class PollResultVotersOverviewViewHolder( } } - private fun getAvatarDraweeController(pollDetail: PollDetails): DraweeController? { - var draweeController: DraweeController? = null + private fun loadAvatar(pollDetail: PollDetails, avatar: ImageView) { if (pollDetail.actorType == "guests") { var displayName = NextcloudTalkApplication.sharedApplication?.resources?.getString(R.string.nc_guest) if (!TextUtils.isEmpty(pollDetail.actorDisplayName)) { displayName = pollDetail.actorDisplayName!! } - draweeController = Fresco.newDraweeControllerBuilder() - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForGuestAvatar( - user.baseUrl, - displayName, - false - ), - user - ) - ) - .build() + avatar.loadGuestAvatar(user, displayName!!, false) } else if (pollDetail.actorType == "users") { - draweeController = Fresco.newDraweeControllerBuilder() - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - user.baseUrl, - pollDetail.actorId, - false - ), - user - ) - ) - .build() + avatar.loadAvatar(user, pollDetail.actorId!!, false) } - return draweeController } companion object { const val AVATAR_SIZE = 60 - const val AVATAR_RADIUS = 5f const val MAX_AVATARS = 10 const val AVATAR_OFFSET = AVATAR_SIZE - 20 const val DOTS_OFFSET = 70 diff --git a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt index 64f62f564..aa6806974 100644 --- a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt +++ b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt @@ -162,7 +162,7 @@ class DirectReplyReceiver : BroadcastReceiver() { val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false) val me = Person.Builder() .setName(currentUser.displayName) - .setIcon(NotificationUtils.loadAvatarSync(avatarUrl)) + .setIcon(NotificationUtils.loadAvatarSync(avatarUrl, context)) .build() val message = NotificationCompat.MessagingStyle.Message(reply, System.currentTimeMillis(), me) previousStyle?.addMessage(message) diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt index bef8be657..b1e10a118 100644 --- a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt @@ -22,20 +22,18 @@ package com.nextcloud.talk.remotefilebrowser.adapters import android.text.format.Formatter import android.view.View +import android.widget.ImageView import autodagger.AutoInjector -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.interfaces.DraweeController -import com.facebook.drawee.view.SimpleDraweeView import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.RvItemBrowserFileBinding +import com.nextcloud.talk.extensions.loadImage import com.nextcloud.talk.remotefilebrowser.SelectionInterface import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DateUtils.getLocalDateTimeStringFromTimestamp -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.Mimetype.FOLDER @AutoInjector(NextcloudTalkApplication::class) @@ -48,7 +46,7 @@ class RemoteFileBrowserItemsListViewHolder( onItemClicked: (Int) -> Unit ) : RemoteFileBrowserItemsViewHolder(binding, mimeTypeSelectionFilter, currentUser, selectionInterface) { - override val fileIcon: SimpleDraweeView + override val fileIcon: ImageView get() = binding.fileIcon private var selectable: Boolean = true @@ -68,7 +66,6 @@ class RemoteFileBrowserItemsListViewHolder( override fun onBind(item: RemoteFileBrowserItem) { super.onBind(item) - binding.fileIcon.controller = null if (!item.isAllowedToReShare || item.isEncrypted) { binding.root.isEnabled = false binding.root.alpha = DISABLED_ALPHA @@ -95,11 +92,7 @@ class RemoteFileBrowserItemsListViewHolder( calculateClickability(item, selectable) setSelectability() - binding.fileIcon - .hierarchy - .setPlaceholderImage( - viewThemeUtils.talk.getPlaceholderImage(binding.root.context, item.mimeType) - ) + val placeholder = viewThemeUtils.talk.getPlaceholderImage(binding.root.context, item.mimeType) if (item.hasPreview) { val path = ApiUtils.getUrlForFilePreviewWithRemotePath( @@ -108,12 +101,10 @@ class RemoteFileBrowserItemsListViewHolder( binding.fileIcon.context.resources.getDimensionPixelSize(R.dimen.small_item_height) ) if (path.isNotEmpty()) { - val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() - .setAutoPlayAnimations(true) - .setImageRequest(DisplayUtils.getImageRequestForUrl(path)) - .build() - binding.fileIcon.controller = draweeController + binding.fileIcon.loadImage(path, currentUser, placeholder) } + } else { + binding.fileIcon.setImageDrawable(placeholder) } binding.filenameTextView.text = item.displayName diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsViewHolder.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsViewHolder.kt index fa76dcccb..027b849aa 100644 --- a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsViewHolder.kt @@ -20,11 +20,9 @@ package com.nextcloud.talk.remotefilebrowser.adapters -import android.graphics.drawable.Drawable -import androidx.core.content.ContextCompat +import android.widget.ImageView import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding -import com.facebook.drawee.view.SimpleDraweeView import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.remotefilebrowser.SelectionInterface import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem @@ -37,17 +35,9 @@ abstract class RemoteFileBrowserItemsViewHolder( val selectionInterface: SelectionInterface, ) : RecyclerView.ViewHolder(binding.root) { - abstract val fileIcon: SimpleDraweeView + abstract val fileIcon: ImageView open fun onBind(item: RemoteFileBrowserItem) { - fileIcon.hierarchy.setPlaceholderImage(staticImage(item.mimeType, fileIcon)) - } - - private fun staticImage( - mimeType: String?, - image: SimpleDraweeView - ): Drawable { - val drawableResourceId = DrawableUtils.getDrawableResourceIdForMimeType(mimeType) - return ContextCompat.getDrawable(image.context, drawableResourceId)!! + fileIcon.setImageResource(DrawableUtils.getDrawableResourceIdForMimeType(item.mimeType)) } } diff --git a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsGridViewHolder.kt b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsGridViewHolder.kt index 30977e6c8..d2b0cb7ad 100644 --- a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsGridViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsGridViewHolder.kt @@ -23,8 +23,8 @@ package com.nextcloud.talk.shareditems.adapters import android.view.View +import android.widget.ImageView import android.widget.ProgressBar -import com.facebook.drawee.view.SimpleDraweeView import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.SharedItemGridBinding import com.nextcloud.talk.ui.theme.ViewThemeUtils @@ -35,7 +35,7 @@ class SharedItemsGridViewHolder( viewThemeUtils: ViewThemeUtils ) : SharedItemsViewHolder(binding, user, viewThemeUtils) { - override val image: SimpleDraweeView + override val image: ImageView get() = binding.image override val clickTarget: View get() = binding.image diff --git a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsListViewHolder.kt b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsListViewHolder.kt index f63549ede..1eb23cf90 100644 --- a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsListViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsListViewHolder.kt @@ -26,9 +26,10 @@ import android.content.Context import android.content.Intent import android.text.format.Formatter import android.view.View +import android.widget.ImageView import android.widget.ProgressBar import androidx.core.content.ContextCompat -import com.facebook.drawee.view.SimpleDraweeView +import coil.load import com.nextcloud.talk.R import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.SharedItemListBinding @@ -46,7 +47,7 @@ class SharedItemsListViewHolder( viewThemeUtils: ViewThemeUtils ) : SharedItemsViewHolder(binding, user, viewThemeUtils) { - override val image: SimpleDraweeView + override val image: ImageView get() = binding.fileImage override val clickTarget: View get() = binding.fileItem @@ -75,7 +76,7 @@ class SharedItemsListViewHolder( binding.separator1.visibility = View.GONE binding.fileDate.text = item.dateTime binding.actor.text = item.actorName - image.hierarchy.setPlaceholderImage(R.drawable.ic_baseline_bar_chart_24) + image.load(R.drawable.ic_baseline_bar_chart_24) image.setColorFilter( ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon), android.graphics.PorterDuff.Mode.SRC_IN @@ -93,7 +94,7 @@ class SharedItemsListViewHolder( binding.separator1.visibility = View.GONE binding.fileDate.text = item.dateTime binding.actor.text = item.actorName - image.hierarchy.setPlaceholderImage(R.drawable.ic_baseline_location_on_24) + image.load(R.drawable.ic_baseline_location_on_24) image.setColorFilter( ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon), android.graphics.PorterDuff.Mode.SRC_IN @@ -114,7 +115,7 @@ class SharedItemsListViewHolder( binding.separator1.visibility = View.GONE binding.fileDate.text = item.dateTime binding.actor.text = item.actorName - image.hierarchy.setPlaceholderImage(R.drawable.ic_mimetype_file) + image.load(R.drawable.ic_mimetype_file) image.setColorFilter( ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon), android.graphics.PorterDuff.Mode.SRC_IN @@ -129,7 +130,7 @@ class SharedItemsListViewHolder( binding.separator1.visibility = View.GONE binding.fileDate.text = item.dateTime binding.actor.text = item.actorName - image.hierarchy.setPlaceholderImage(R.drawable.ic_baseline_deck_24) + image.load(R.drawable.ic_baseline_deck_24) image.setColorFilter( ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon), android.graphics.PorterDuff.Mode.SRC_IN diff --git a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt index a1861cdaa..c699e3595 100644 --- a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt @@ -23,29 +23,20 @@ package com.nextcloud.talk.shareditems.adapters import android.content.Context -import android.net.Uri -import android.util.Log import android.view.View +import android.widget.ImageView import android.widget.ProgressBar import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.controller.BaseControllerListener -import com.facebook.drawee.controller.ControllerListener -import com.facebook.drawee.interfaces.DraweeController -import com.facebook.drawee.view.SimpleDraweeView -import com.facebook.imagepipeline.common.RotationOptions -import com.facebook.imagepipeline.image.ImageInfo -import com.facebook.imagepipeline.request.ImageRequestBuilder import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.extensions.loadImage import com.nextcloud.talk.shareditems.model.SharedDeckCardItem import com.nextcloud.talk.shareditems.model.SharedFileItem import com.nextcloud.talk.shareditems.model.SharedItem -import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.shareditems.model.SharedLocationItem import com.nextcloud.talk.shareditems.model.SharedOtherItem import com.nextcloud.talk.shareditems.model.SharedPollItem -import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.FileViewerUtils abstract class SharedItemsViewHolder( @@ -58,21 +49,21 @@ abstract class SharedItemsViewHolder( private val TAG = SharedItemsViewHolder::class.simpleName } - abstract val image: SimpleDraweeView + abstract val image: ImageView abstract val clickTarget: View abstract val progressBar: ProgressBar - private val authHeader = mapOf( - Pair( - "Authorization", - ApiUtils.getCredentials(user.username, user.token) - ) - ) - open fun onBind(item: SharedFileItem) { - image.hierarchy.setPlaceholderImage(viewThemeUtils.talk.getPlaceholderImage(image.context, item.mimeType)) + + val placeholder = viewThemeUtils.talk.getPlaceholderImage(image.context, item.mimeType) if (item.previewAvailable) { - image.controller = configurePreview(item) + image.loadImage( + item.previewLink, + user, + placeholder + ) + } else { + image.setImageDrawable(placeholder) } /* @@ -105,28 +96,6 @@ abstract class SharedItemsViewHolder( ) } - private fun configurePreview(item: SharedFileItem): DraweeController { - val imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(item.previewLink)) - .setProgressiveRenderingEnabled(true) - .setRotationOptions(RotationOptions.autoRotate()) - .disableDiskCache() - .setHeaders(authHeader) - .build() - - val listener: ControllerListener = object : BaseControllerListener() { - override fun onFailure(id: String, e: Throwable) { - Log.w(TAG, "Failed to load image. A static mimetype image will be used", e) - } - } - - return Fresco.newDraweeControllerBuilder() - .setOldController(image.controller) - .setAutoPlayAnimations(true) - .setImageRequest(imageRequest) - .setControllerListener(listener) - .build() - } - open fun onBind(item: SharedPollItem, showPoll: (item: SharedItem, context: Context) -> Unit) {} open fun onBind(item: SharedLocationItem) {} diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java index c42b711b3..d75c99403 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java @@ -33,8 +33,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.interfaces.DraweeController; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.nextcloud.talk.activities.MainActivity; import com.nextcloud.talk.adapters.items.AdvancedUserItem; @@ -42,6 +40,7 @@ import com.nextcloud.talk.api.NcApi; import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.databinding.DialogChooseAccountBinding; +import com.nextcloud.talk.extensions.ImageViewExtensionsKt; import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.models.json.status.Status; import com.nextcloud.talk.models.json.status.StatusOverall; @@ -132,22 +131,10 @@ public class ChooseAccountDialogFragment extends DialogFragment { if (user.getBaseUrl() != null && (user.getBaseUrl().startsWith("http://") || user.getBaseUrl().startsWith("https://"))) { binding.currentAccount.userIcon.setVisibility(View.VISIBLE); - - DraweeController draweeController = Fresco.newDraweeControllerBuilder() - .setOldController(binding.currentAccount.userIcon.getController()) - .setAutoPlayAnimations(true) - .setImageRequest(DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - user.getBaseUrl(), - user.getUserId(), - false))) - .build(); - binding.currentAccount.userIcon.setController(draweeController); - + ImageViewExtensionsKt.loadAvatar(binding.currentAccount.userIcon, user, user.getUserId(), true); } else { binding.currentAccount.userIcon.setVisibility(View.INVISIBLE); } - loadCurrentStatus(user); } } diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt index 90bdba9dd..38a90a99b 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt @@ -33,8 +33,6 @@ import android.view.ViewGroup import androidx.fragment.app.DialogFragment import androidx.recyclerview.widget.LinearLayoutManager import autodagger.AutoInjector -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.interfaces.DraweeController import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.nextcloud.talk.activities.MainActivity import com.nextcloud.talk.adapters.items.AdvancedUserItem @@ -42,11 +40,10 @@ import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.DialogChooseAccountShareToBinding +import com.nextcloud.talk.extensions.loadAvatar import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.users.UserManager -import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import java.net.CookieManager @@ -97,21 +94,7 @@ class ChooseAccountShareToDialogFragment : DialogFragment() { if (user.baseUrl != null && (user.baseUrl!!.startsWith("http://") || user.baseUrl!!.startsWith("https://")) ) { - binding!!.currentAccount.userIcon.visibility = View.VISIBLE - val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() - .setOldController(binding!!.currentAccount.userIcon.controller) - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - user.baseUrl, - user.userId, - false - ) - ) - ) - .build() - binding!!.currentAccount.userIcon.controller = draweeController + binding!!.currentAccount.userIcon.loadAvatar(user, user.userId!!) } else { binding!!.currentAccount.userIcon.visibility = View.INVISIBLE } diff --git a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java index cb363337d..fdd4d5605 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java @@ -3,6 +3,8 @@ * * @author Mario Danic * @author Andy Scherzinger + * @author Tim Krüger + * Copyright (C) 2022 Tim Krüger * Copyright (C) 2021 Andy Scherzinger * Copyright (C) 2017-2020 Mario Danic * @@ -33,11 +35,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Typeface; -import android.graphics.drawable.Animatable; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.graphics.drawable.VectorDrawable; import android.net.Uri; import android.os.Build; import android.text.Spannable; @@ -51,36 +49,19 @@ import android.text.style.AbsoluteSizeSpan; import android.text.style.ClickableSpan; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; -import android.util.Log; import android.util.TypedValue; import android.view.View; -import android.view.ViewGroup; import android.view.Window; import android.widget.EditText; +import android.widget.ImageView; import android.widget.TextView; -import com.facebook.common.executors.UiThreadImmediateExecutorService; -import com.facebook.common.references.CloseableReference; -import com.facebook.datasource.DataSource; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.controller.ControllerListener; -import com.facebook.drawee.interfaces.DraweeController; -import com.facebook.drawee.view.SimpleDraweeView; -import com.facebook.imagepipeline.common.RotationOptions; -import com.facebook.imagepipeline.core.ImagePipeline; -import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber; -import com.facebook.imagepipeline.image.CloseableImage; -import com.facebook.imagepipeline.image.ImageInfo; -import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor; -import com.facebook.imagepipeline.postprocessors.RoundPostprocessor; -import com.facebook.imagepipeline.request.ImageRequest; -import com.facebook.imagepipeline.request.ImageRequestBuilder; -import com.facebook.widget.text.span.BetterImageSpan; import com.google.android.material.chip.ChipDrawable; import com.nextcloud.talk.R; import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.events.UserMentionClickEvent; +import com.nextcloud.talk.extensions.ImageViewExtensionsKt; import com.nextcloud.talk.ui.theme.ViewThemeUtils; import com.nextcloud.talk.utils.text.Spans; @@ -91,8 +72,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.DateFormat; import java.util.Date; -import java.util.HashMap; -import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -109,6 +88,11 @@ import androidx.core.content.res.ResourcesCompat; import androidx.core.graphics.ColorUtils; import androidx.core.graphics.drawable.DrawableCompat; import androidx.emoji.text.EmojiCompat; +import coil.Coil; +import coil.request.ImageRequest; +import coil.target.Target; +import coil.transform.CircleCropTransformation; +import third.parties.fresco.BetterImageSpan; import static com.nextcloud.talk.utils.FileSortOrder.sort_a_to_z_id; import static com.nextcloud.talk.utils.FileSortOrder.sort_big_to_small_id; @@ -119,8 +103,6 @@ import static com.nextcloud.talk.utils.FileSortOrder.sort_z_to_a_id; public class DisplayUtils { - private static final String TAG = "DisplayUtils"; - private static final int INDEX_LUMINATION = 2; private static final double MAX_LIGHTNESS = 0.92; @@ -154,33 +136,6 @@ public class DisplayUtils { textView.setMovementMethod(LinkMovementMethod.getInstance()); } - private static void updateViewSize(@Nullable ImageInfo imageInfo, SimpleDraweeView draweeView) { - if (imageInfo != null && draweeView.getId() != R.id.messageUserAvatar) { - int maxSize = draweeView.getContext().getResources().getDimensionPixelSize(R.dimen.maximum_file_preview_size); - draweeView.getLayoutParams().width = imageInfo.getWidth() > maxSize ? maxSize : imageInfo.getWidth(); - draweeView.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT; - draweeView.setAspectRatio((float) imageInfo.getWidth() / imageInfo.getHeight()); - draweeView.requestLayout(); - } - } - - public static Drawable getRoundedDrawable(Drawable drawable) { - Bitmap bitmap = getBitmap(drawable); - new RoundAsCirclePostprocessor(true).process(bitmap); - return new BitmapDrawable(bitmap); - } - - public static Bitmap getRoundedBitmapFromVectorDrawableResource(Resources resources, int resource) { - VectorDrawable vectorDrawable = (VectorDrawable) ResourcesCompat.getDrawable(resources, resource, null); - Bitmap bitmap = getBitmap(vectorDrawable); - new RoundPostprocessor(true).process(bitmap); - return bitmap; - } - - public static Drawable getRoundedBitmapDrawableFromVectorDrawableResource(Resources resources, int resource) { - return new BitmapDrawable(getRoundedBitmapFromVectorDrawableResource(resources, resource)); - } - public static Bitmap getBitmap(Drawable drawable) { Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), @@ -191,60 +146,6 @@ public class DisplayUtils { return bitmap; } - public static ImageRequest getImageRequestForUrl(String url) { - return getImageRequestForUrl(url, (User) null); - } - - public static ImageRequest getImageRequestForUrl(String url, @Nullable User user) { - Map headers = new HashMap<>(); - if (user != null && - url.startsWith(user.getBaseUrl()) && - (url.contains("index.php/core/preview?fileId=") || url.contains("/avatar/"))) { - headers.put("Authorization", ApiUtils.getCredentials(user.getUsername(), user.getToken())); - } - - return ImageRequestBuilder.newBuilderWithSource(Uri.parse(url)) - .setProgressiveRenderingEnabled(true) - .setRotationOptions(RotationOptions.autoRotate()) - .disableDiskCache() - .setHeaders(headers) - .build(); - } - - public static ControllerListener getImageControllerListener(SimpleDraweeView draweeView) { - return new ControllerListener() { - @Override - public void onSubmit(String id, Object callerContext) { - // unused atm - } - - @Override - public void onFinalImageSet(String id, @Nullable Object imageInfo, @Nullable Animatable animatable) { - updateViewSize((ImageInfo) imageInfo, draweeView); - } - - @Override - public void onIntermediateImageSet(String id, @Nullable Object imageInfo) { - updateViewSize((ImageInfo) imageInfo, draweeView); - } - - @Override - public void onIntermediateImageFailed(String id, Throwable throwable) { - // unused atm - } - - @Override - public void onFailure(String id, Throwable throwable) { - // unused atm - } - - @Override - public void onRelease(String id) { - // unused atm - } - }; - } - public static float convertDpToPixel(float dp, Context context) { return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()) + 0.5f); @@ -335,33 +236,37 @@ public class DisplayUtils { conversationUser.getBaseUrl(), String.valueOf(label), true); } - ImageRequest imageRequest = getImageRequestForUrl(url); - ImagePipeline imagePipeline = Fresco.getImagePipeline(); - DataSource> dataSource = imagePipeline.fetchDecodedImage( - imageRequest, - context); - dataSource.subscribe( - new BaseBitmapDataSubscriber() { + ImageRequest imageRequest = new ImageRequest.Builder(context) + .data(url) + .crossfade(true) + .transformations(new CircleCropTransformation()) + .target(new Target() { @Override - protected void onNewResultImpl(Bitmap bitmap) { - if (bitmap != null) { - chip.setChipIcon(getRoundedDrawable(new BitmapDrawable(bitmap))); + public void onStart(@Nullable Drawable drawable) { - // A hack to refresh the chip icon - if (emojiEditText != null) { - emojiEditText.post(() -> emojiEditText.setTextKeepState( - emojiEditText.getText(), - TextView.BufferType.SPANNABLE)); - } + } + + @Override + public void onError(@Nullable Drawable drawable) { + + } + + @Override + public void onSuccess(@NonNull Drawable drawable) { + chip.setChipIcon(drawable); + + // A hack to refresh the chip icon + if (emojiEditText != null) { + emojiEditText.post(() -> emojiEditText.setTextKeepState( + emojiEditText.getText(), + TextView.BufferType.SPANNABLE)); } } + }) + .build(); - @Override - protected void onFailureImpl(DataSource> dataSource) { - } - }, - UiThreadImmediateExecutorService.getInstance()); + Coil.imageLoader(context).enqueue(imageRequest); } return chip; @@ -575,7 +480,7 @@ public class DisplayUtils { } } - public static void loadAvatarImage(User user, SimpleDraweeView avatarImageView, boolean deleteCache) { + public static void loadAvatarImage(User user, ImageView avatarImageView, boolean deleteCache) { String avatarId; if (!TextUtils.isEmpty(user.getUserId())) { avatarId = user.getUserId(); @@ -583,50 +488,13 @@ public class DisplayUtils { avatarId = user.getUsername(); } - String avatarString = ApiUtils.getUrlForAvatar(user.getBaseUrl(), avatarId, true); - - // clear cache if (deleteCache) { - Uri avatarUri = Uri.parse(avatarString); - - ImagePipeline imagePipeline = Fresco.getImagePipeline(); - imagePipeline.evictFromMemoryCache(avatarUri); - imagePipeline.evictFromDiskCache(avatarUri); - imagePipeline.evictFromCache(avatarUri); - } - - DraweeController draweeController = Fresco.newDraweeControllerBuilder() - .setOldController(avatarImageView.getController()) - .setAutoPlayAnimations(true) - .setImageRequest(DisplayUtils.getImageRequestForUrl(avatarString)) - .build(); - avatarImageView.setController(draweeController); - } - - public static void loadAvatarPlaceholder(final SimpleDraweeView targetView) { - final Context context = targetView.getContext(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - Drawable[] layers = new Drawable[2]; - layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background); - layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground); - LayerDrawable layerDrawable = new LayerDrawable(layers); - - targetView.getHierarchy().setPlaceholderImage( - DisplayUtils.getRoundedDrawable(layerDrawable)); + ImageViewExtensionsKt.replaceAvatar(avatarImageView, user, avatarId, true); } else { - targetView.getHierarchy().setPlaceholderImage(R.mipmap.ic_launcher); + ImageViewExtensionsKt.loadAvatar(avatarImageView, user, avatarId, true); } } - public static void loadImage(final SimpleDraweeView targetView, final ImageRequest imageRequest) { - final DraweeController newController = Fresco.newDraweeControllerBuilder() - .setOldController(targetView.getController()) - .setAutoPlayAnimations(true) - .setImageRequest(imageRequest) - .build(); - targetView.setController(newController); - } - public static @StringRes int getSortOrderStringId(FileSortOrder sortOrder) { switch (sortOrder.getName()) { @@ -649,7 +517,7 @@ public class DisplayUtils { /** * calculates the relative time string based on the given modification timestamp. * - * @param context the app's context + * @param context the app's context * @param modificationTimestamp the UNIX timestamp of the file modification time in milliseconds. * @return a relative time string */ diff --git a/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByName.kt b/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByName.kt index 2b4f3524a..487c4f3c7 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByName.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByName.kt @@ -22,7 +22,7 @@ package com.nextcloud.talk.utils import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem -import third_parties.daveKoeller.AlphanumComparator +import third.parties.daveKoeller.AlphanumComparator import java.util.Collections class FileSortOrderByName internal constructor(name: String, ascending: Boolean) : FileSortOrder(name, ascending) { @@ -40,7 +40,8 @@ class FileSortOrderByName internal constructor(name: String, ascending: Boolean) * Comparator for RemoteFileBrowserItems, sorts by name. */ class RemoteFileBrowserItemNameComparator(private val multiplier: Int) : Comparator { - private val alphanumComparator = AlphanumComparator() + private val alphanumComparator = + AlphanumComparator() override fun compare(left: RemoteFileBrowserItem, right: RemoteFileBrowserItem): Int { return if (!left.isFile && !right.isFile) { diff --git a/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt index 35d1f2998..098c2dceb 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt @@ -28,6 +28,7 @@ import android.net.Uri import android.os.Build import android.util.Log import android.view.View +import android.widget.ImageView import android.widget.ProgressBar import android.widget.Toast import androidx.core.content.FileProvider @@ -36,7 +37,6 @@ import androidx.work.Data import androidx.work.OneTimeWorkRequest import androidx.work.WorkInfo import androidx.work.WorkManager -import com.facebook.drawee.view.SimpleDraweeView import com.nextcloud.talk.R import com.nextcloud.talk.activities.FullScreenImageActivity import com.nextcloud.talk.activities.FullScreenMediaActivity @@ -412,7 +412,7 @@ class FileViewerUtils(private val context: Context, private val user: User) { data class ProgressUi( val progressBar: ProgressBar?, val messageText: EmojiTextView?, - val previewImage: SimpleDraweeView + val previewImage: ImageView ) data class FileInfo( diff --git a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt index 62bbc837c..dbd264dea 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt @@ -27,18 +27,19 @@ import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context +import android.graphics.drawable.BitmapDrawable import android.media.AudioAttributes import android.net.Uri import android.os.Build import android.service.notification.StatusBarNotification import android.text.TextUtils +import android.util.Log import androidx.core.graphics.drawable.IconCompat +import coil.executeBlocking +import coil.imageLoader +import coil.request.ImageRequest +import coil.transform.CircleCropTransformation import com.bluelinelabs.logansquare.LoganSquare -import com.facebook.common.references.CloseableReference -import com.facebook.datasource.DataSources -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.imagepipeline.image.CloseableBitmap -import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.R import com.nextcloud.talk.data.user.model.User @@ -50,6 +51,8 @@ import java.io.IOException @Suppress("TooManyFunctions") object NotificationUtils { + const val TAG = "NotificationUtils" + enum class NotificationChannels { NOTIFICATION_CHANNEL_MESSAGES_V4, NOTIFICATION_CHANNEL_CALLS_V4, @@ -320,28 +323,31 @@ object NotificationUtils { ) } - /* - * Load user avatar synchronously. - * Inspired by: - * https://frescolib.org/docs/using-image-pipeline.html - * https://github.com/facebook/fresco/issues/830 - * https://localcoder.org/using-facebooks-fresco-to-load-a-bitmap - */ - fun loadAvatarSync(avatarUrl: String): IconCompat? { - // TODO - how to handle errors here? + fun loadAvatarSync(avatarUrl: String, context: Context): IconCompat? { var avatarIcon: IconCompat? = null - val imageRequest = DisplayUtils.getImageRequestForUrl(avatarUrl) - val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, null) - val closeableImageRef = DataSources.waitForFinalResult(dataSource) as CloseableReference? - val bitmap = closeableImageRef?.get()?.underlyingBitmap - if (bitmap != null) { - // According to Fresco documentation a copy of the bitmap should be made before closing the references. - // However, it seems to work without making a copy... ;-) - RoundAsCirclePostprocessor(true).process(bitmap) - avatarIcon = IconCompat.createWithBitmap(bitmap) - } - CloseableReference.closeSafely(closeableImageRef) - dataSource.close() + + val request = ImageRequest.Builder(context) + .data(avatarUrl) + .transformations(CircleCropTransformation()) + .placeholder(R.drawable.account_circle_96dp) + .placeholder(R.drawable.account_circle_96dp) + .target( + onSuccess = { result -> + val bitmap = (result as BitmapDrawable).bitmap + avatarIcon = IconCompat.createWithBitmap(bitmap) + }, + onError = { error -> + error?.let { + val bitmap = (error as BitmapDrawable).bitmap + avatarIcon = IconCompat.createWithBitmap(bitmap) + } + Log.w(TAG, "Can't load avatar for URL: $avatarUrl") + } + ) + .build() + + context.imageLoader.executeBlocking(request) + return avatarIcon } diff --git a/app/src/main/java/com/nextcloud/talk/utils/OkHttpNetworkFetcherWithCache.java b/app/src/main/java/com/nextcloud/talk/utils/OkHttpNetworkFetcherWithCache.java deleted file mode 100644 index 82e483cf0..000000000 --- a/app/src/main/java/com/nextcloud/talk/utils/OkHttpNetworkFetcherWithCache.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * 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 . - */ - -package com.nextcloud.talk.utils; - -import com.facebook.imagepipeline.backends.okhttp3.OkHttpNetworkFetcher; -import okhttp3.Call; -import okhttp3.OkHttpClient; - -import java.util.concurrent.Executor; - -public class OkHttpNetworkFetcherWithCache extends OkHttpNetworkFetcher { - public OkHttpNetworkFetcherWithCache(OkHttpClient okHttpClient) { - super(okHttpClient); - } - - public OkHttpNetworkFetcherWithCache(Call.Factory callFactory, Executor cancellationExecutor) { - super(callFactory, cancellationExecutor); - } - - public OkHttpNetworkFetcherWithCache(Call.Factory callFactory, Executor cancellationExecutor, boolean disableOkHttpCache) { - super(callFactory, cancellationExecutor, true); - } -} diff --git a/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java b/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java index 077ebe857..50d6bfa07 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java +++ b/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java @@ -22,9 +22,10 @@ package com.nextcloud.talk.utils.text; import android.graphics.drawable.Drawable; -import com.facebook.widget.text.span.BetterImageSpan; + import androidx.annotation.NonNull; +import third.parties.fresco.BetterImageSpan; public class Spans { diff --git a/app/src/main/java/third_parties/daveKoeller/AlphanumComparator.java b/app/src/main/java/third/parties/daveKoeller/AlphanumComparator.java similarity index 99% rename from app/src/main/java/third_parties/daveKoeller/AlphanumComparator.java rename to app/src/main/java/third/parties/daveKoeller/AlphanumComparator.java index 35cd76177..59eac62ea 100644 --- a/app/src/main/java/third_parties/daveKoeller/AlphanumComparator.java +++ b/app/src/main/java/third/parties/daveKoeller/AlphanumComparator.java @@ -22,7 +22,7 @@ * */ -package third_parties.daveKoeller; +package third.parties.daveKoeller; import java.io.Serializable; import java.math.BigInteger; diff --git a/app/src/main/java/third_parties/daveKoeller/lgpl-2.1.txt b/app/src/main/java/third/parties/daveKoeller/lgpl-2.1.txt similarity index 100% rename from app/src/main/java/third_parties/daveKoeller/lgpl-2.1.txt rename to app/src/main/java/third/parties/daveKoeller/lgpl-2.1.txt diff --git a/app/src/main/java/third/parties/fresco/BetterImageSpan.kt b/app/src/main/java/third/parties/fresco/BetterImageSpan.kt new file mode 100644 index 000000000..fbd003ed0 --- /dev/null +++ b/app/src/main/java/third/parties/fresco/BetterImageSpan.kt @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * MIT License + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package third.parties.fresco + +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Paint.FontMetricsInt +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.text.style.ReplacementSpan +import androidx.annotation.IntDef + +/** + * A better implementation of image spans that also supports centering images against the text. + * + * In order to migrate from ImageSpan, replace `new ImageSpan(drawable, alignment)` with + * `new BetterImageSpan(drawable, BetterImageSpan.normalizeAlignment(alignment))`. + * + * There are 2 main differences between BetterImageSpan and ImageSpan: + * 1. Pass in ALIGN_CENTER to center images against the text. + * 2. ALIGN_BOTTOM no longer unnecessarily increases the size of the text: + * DynamicDrawableSpan (ImageSpan's parent) adjusts sizes as if alignment was ALIGN_BASELINE + * which can lead to unnecessary whitespace. + */ +open class BetterImageSpan @JvmOverloads constructor( + val drawable: Drawable, + @param:BetterImageSpanAlignment private val mAlignment: Int = ALIGN_BASELINE +) : ReplacementSpan() { + @IntDef(*[ALIGN_BASELINE, ALIGN_BOTTOM, ALIGN_CENTER]) + @Retention(AnnotationRetention.SOURCE) + annotation class BetterImageSpanAlignment + + private var mWidth = 0 + private var mHeight = 0 + private var mBounds: Rect? = null + private val mFontMetricsInt = FontMetricsInt() + + init { + updateBounds() + } + + /** + * Returns the width of the image span and increases the height if font metrics are available. + */ + override fun getSize( + paint: Paint, + text: CharSequence, + start: Int, + end: Int, + fontMetrics: FontMetricsInt? + ): Int { + updateBounds() + if (fontMetrics == null) { + return mWidth + } + val offsetAbove = getOffsetAboveBaseline(fontMetrics) + val offsetBelow = mHeight + offsetAbove + if (offsetAbove < fontMetrics.ascent) { + fontMetrics.ascent = offsetAbove + } + if (offsetAbove < fontMetrics.top) { + fontMetrics.top = offsetAbove + } + if (offsetBelow > fontMetrics.descent) { + fontMetrics.descent = offsetBelow + } + if (offsetBelow > fontMetrics.bottom) { + fontMetrics.bottom = offsetBelow + } + return mWidth + } + + override fun draw( + canvas: Canvas, + text: CharSequence, + start: Int, + end: Int, + x: Float, + top: Int, + y: Int, + bottom: Int, + paint: Paint + ) { + paint.getFontMetricsInt(mFontMetricsInt) + val iconTop = y + getOffsetAboveBaseline(mFontMetricsInt) + canvas.translate(x, iconTop.toFloat()) + drawable.draw(canvas) + canvas.translate(-x, -iconTop.toFloat()) + } + + private fun updateBounds() { + mBounds = drawable.bounds + mWidth = mBounds!!.width() + mHeight = mBounds!!.height() + } + + private fun getOffsetAboveBaseline(fm: FontMetricsInt): Int { + return when (mAlignment) { + ALIGN_BOTTOM -> fm.descent - mHeight + ALIGN_CENTER -> { + val textHeight = fm.descent - fm.ascent + val offset = (textHeight - mHeight) / 2 + fm.ascent + offset + } + ALIGN_BASELINE -> -mHeight + else -> -mHeight + } + } + + companion object { + const val ALIGN_BOTTOM = 0 + const val ALIGN_BASELINE = 1 + const val ALIGN_CENTER = 2 + } +} diff --git a/app/src/main/res/drawable/shape_oval.xml b/app/src/main/res/drawable/shape_oval.xml new file mode 100644 index 000000000..df8bfd8bd --- /dev/null +++ b/app/src/main/res/drawable/shape_oval.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/account_item.xml b/app/src/main/res/layout/account_item.xml index 1f4b04761..8668dbcb9 100644 --- a/app/src/main/res/layout/account_item.xml +++ b/app/src/main/res/layout/account_item.xml @@ -21,7 +21,6 @@ --> - + android:src="@drawable/account_circle_48dp" /> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 55fab6b8a..430439fdb 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -113,7 +113,8 @@ android:transitionName="userAvatar.transitionTag" app:cornerRadius="@dimen/button_corner_radius" app:iconSize="@dimen/avatar_size_app_bar" - tools:visibility="gone" /> + app:iconTint="@null" + tools:icon="@drawable/ic_user" /> diff --git a/app/src/main/res/layout/call_activity.xml b/app/src/main/res/layout/call_activity.xml index e7dc38665..2a3f3b71b 100644 --- a/app/src/main/res/layout/call_activity.xml +++ b/app/src/main/res/layout/call_activity.xml @@ -2,9 +2,11 @@ ~ Nextcloud Talk application ~ ~ @author Mario Danic - ~ Copyright (C) 2017-2018 Mario Danic ~ @author Marcel Hibbe + ~ @author Tim Krüger + ~ Copyright (C) 2022 Tim Krüger ~ Copyright (C) 2021 Marcel Hibbe + ~ Copyright (C) 2017-2018 Mario Danic ~ ~ 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 @@ -69,14 +71,14 @@ android:visibility="invisible" tools:visibility="visible" /> - + app:srcCompat="@drawable/ic_switch_video_white_24px" + android:contentDescription="@string/nc_call_button_content_description_switch_to_self_vide"/> - + android:layout_height="wrap_content" + android:adjustViewBounds="true" + android:layout_marginHorizontal="@dimen/call_controls_margin_horizontal" + android:layout_weight="1" + android:background="@drawable/shape_oval" + android:backgroundTint="@color/call_buttons_background" + app:srcCompat="@drawable/ic_baseline_picture_in_picture_alt_24" + android:contentDescription="@string/nc_call_button_content_description_pip" /> - + android:layout_height="wrap_content" + android:adjustViewBounds="true" + android:layout_marginHorizontal="@dimen/call_controls_margin_horizontal" + android:layout_weight="1" + android:background="@drawable/shape_oval" + android:backgroundTint="@color/call_buttons_background" + app:srcCompat="@drawable/ic_volume_mute_white_24dp" + android:contentDescription="@string/nc_call_button_content_description_audio_output" /> - + android:background="@drawable/shape_oval" + android:backgroundTint="@color/call_buttons_background" + app:srcCompat="@drawable/ic_videocam_white_24px" + android:contentDescription="@string/nc_call_button_content_description_camera" /> - + android:background="@drawable/shape_oval" + android:backgroundTint="@color/call_buttons_background" + app:srcCompat="@drawable/ic_mic_off_white_24px" + android:contentDescription="@string/nc_call_button_content_description_microphone" /> - + android:layout_height="wrap_content" + android:adjustViewBounds="true" + android:layout_marginHorizontal="@dimen/call_controls_margin_horizontal" + android:layout_weight="1" + android:background="@drawable/shape_oval" + android:backgroundTint="@color/nc_darkRed" + app:srcCompat="@drawable/ic_call_end_white_24px" + android:contentDescription="@string/nc_call_button_content_description_hangup" /> - + app:srcCompat="@drawable/ic_circular_group" + tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout/call_item.xml b/app/src/main/res/layout/call_item.xml index 41f175472..8b87a7eac 100644 --- a/app/src/main/res/layout/call_item.xml +++ b/app/src/main/res/layout/call_item.xml @@ -31,12 +31,12 @@ android:gravity="center" android:orientation="vertical"> - + android:contentDescription="@string/avatar"/> ~ Copyright (C) 2021 Andy Scherzinger ~ Copyright (C) 2017-2018 Mario Danic ~ @@ -36,36 +38,37 @@ android:animateLayoutChanges="true" android:orientation="horizontal"> - + android:background="@drawable/shape_oval" + android:backgroundTint="@color/nc_darkGreen" + android:src="@drawable/ic_call_white_24dp" + android:contentDescription="@string/nc_call_button_content_description_answer_voice_only" /> - + android:background="@drawable/shape_oval" + android:backgroundTint="@color/nc_darkRed" + android:src="@drawable/ic_call_end_white_24px" + android:contentDescription="@string/nc_call_button_content_description_hangup" /> - + tools:visibility="visible" + android:contentDescription="@string/nc_call_button_content_description_answer_video_call" /> - + android:contentDescription="@string/avatar" /> diff --git a/app/src/main/res/layout/controller_conversation_info.xml b/app/src/main/res/layout/controller_conversation_info.xml index 6ae406ad2..53d8fd534 100644 --- a/app/src/main/res/layout/controller_conversation_info.xml +++ b/app/src/main/res/layout/controller_conversation_info.xml @@ -77,13 +77,13 @@ android:layout_marginTop="@dimen/margin_between_elements" tools:text="Jane Doe" /> - + tools:background="@color/hwSecurityRed" + android:contentDescription="@string/avatar" /> diff --git a/app/src/main/res/layout/controller_profile.xml b/app/src/main/res/layout/controller_profile.xml index d09b65b7d..f401adaf3 100644 --- a/app/src/main/res/layout/controller_profile.xml +++ b/app/src/main/res/layout/controller_profile.xml @@ -19,7 +19,6 @@ --> - + android:contentDescription="@string/avatar" /> - + android:contentDescription="@string/avatar" /> - + android:src="@drawable/account_circle_48dp" /> - + android:contentDescription="@string/avatar" /> - + android:contentDescription="@string/avatar" /> - + android:contentDescription="@string/avatar" /> - + android:contentDescription="@string/avatar" /> - - + tools:src="@drawable/ic_call_black_24dp" + tools:ignore="ContentDescription" /> - - + tools:src="@drawable/ic_call_black_24dp" + tools:ignore="ContentDescription" /> - + android:contentDescription="@string/avatar" /> - + android:contentDescription="@string/avatar" /> - - + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scaleType="fitStart" + tools:src="@drawable/ic_call_black_24dp" + tools:ignore="ContentDescription" /> - - + tools:src="@drawable/ic_call_black_24dp" + tools:ignore="ContentDescription" /> - + android:contentDescription="@string/avatar"/> - + android:contentDescription="@string/avatar"/> + tools:layout_height="100dp" /> + tools:visibility="visible" /> + tools:visibility="visible" /> + tools:visibility="visible" /> - + tools:visibility="visible" + tools:ignore="ContentDescription" /> \ No newline at end of file diff --git a/app/src/main/res/layout/rv_item_browser_file.xml b/app/src/main/res/layout/rv_item_browser_file.xml index 08958cdff..74bf7c065 100644 --- a/app/src/main/res/layout/rv_item_browser_file.xml +++ b/app/src/main/res/layout/rv_item_browser_file.xml @@ -93,13 +93,12 @@ android:textSize="@dimen/two_line_primary_text_size" tools:text="filename.md" /> - + tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout/rv_item_contact.xml b/app/src/main/res/layout/rv_item_contact.xml index 7bcefd7e7..4abb3ecee 100644 --- a/app/src/main/res/layout/rv_item_contact.xml +++ b/app/src/main/res/layout/rv_item_contact.xml @@ -48,19 +48,19 @@ android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toStartOf="@id/checkedImageView" - android:layout_toEndOf="@id/avatar_drawee_view" + android:layout_toEndOf="@id/avatar_view" android:ellipsize="end" android:lines="1" android:textAlignment="viewStart" android:textAppearance="@style/ListItem" tools:text="Jane Doe" /> - + android:contentDescription="@string/avatar" /> diff --git a/app/src/main/res/layout/rv_item_contact_shimmer.xml b/app/src/main/res/layout/rv_item_contact_shimmer.xml index d392feb5a..417efe7bd 100644 --- a/app/src/main/res/layout/rv_item_contact_shimmer.xml +++ b/app/src/main/res/layout/rv_item_contact_shimmer.xml @@ -29,7 +29,7 @@ android:orientation="vertical"> diff --git a/app/src/main/res/layout/rv_item_conversation_info_participant.xml b/app/src/main/res/layout/rv_item_conversation_info_participant.xml index c72f1b1d3..494a1c608 100644 --- a/app/src/main/res/layout/rv_item_conversation_info_participant.xml +++ b/app/src/main/res/layout/rv_item_conversation_info_participant.xml @@ -26,15 +26,14 @@ android:layout_marginBottom="@dimen/standard_half_margin" android:layout_marginTop="@dimen/standard_margin"> - + app:layout_constraintTop_toTopOf="parent" /> ~ Copyright (C) 2021 Andy Scherzinger ~ Copyright (C) 2017-2018 Mario Danic ~ @@ -38,12 +40,11 @@ android:layout_centerVertical="true" android:layout_marginEnd="@dimen/double_margin_between_elements"> - + android:contentDescription="@null" /> + app:layout_constraintTop_toTopOf="parent" /> ~ Copyright (C) 2021 Andy Scherzinger ~ Copyright (C) 2017-2018 Mario Danic ~ @@ -35,14 +37,13 @@ android:layout_margin="@dimen/double_margin_between_elements" tools:background="@color/white"> - + app:layout_constraintTop_toTopOf="parent" /> @@ -40,14 +40,12 @@ app:layout_flexGrow="1" app:layout_wrapBefore="true"> - + tools:ignore="ContentDescription" /> - + tools:src="@drawable/ic_call_black_24dp" + tools:ignore="ContentDescription" /> ~ Copyright (C) 2017-2019 Mario Danic ~ ~ This program is free software: you can redistribute it and/or modify @@ -67,6 +69,8 @@ 80dp 180dp 110dp + 10dp + 10dp 48dp 48dp 0dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9fa3b5a2c..40ec2036e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,6 +4,8 @@ ~ @author Mario Danic ~ @author Andy Scherzinger ~ @author Marcel Hibbe + ~ @author Tim Krüger + ~ Copyright (C) 2022 Tim Krüger ~ Copyright (C) 2022 Marcel Hibbe ~ Copyright (C) 2021 Andy Scherzinger ~ Copyright (C) 2017-2018 Mario Danic @@ -211,6 +213,14 @@ %1$s with phone %1$s with video You missed a call from %s + Open picture in picture mode + Change audio output + Toggle camera + Toggle microphone + Hangup + Answer as voice call only + Answer as video call + Switch to self video Mute microphone