From 49da4639719952ee04a71870407bc950c41232ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Fri, 14 Oct 2022 11:53:48 +0200 Subject: [PATCH] Replace Fresco with Coil MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fresco is replaced with Coil everywhere to make it possible to set 'minSdkVersion' to 23. But Coil is not used directly to avoid splintering the dependency everywhere in the code. Coil is wrapped by extension functions for 'ImageView'. Some shared functionality is moved from 'DisplayUtils' into the 'ImageViewExtensions'. The exisiting initialization of Coil has also be changed. The usage of the self initialized OKHttp client is removed. If this one is added the caching of the http client is used by Coil additionally to memory and disk cache. Resolves: #2227, #2376 Signed-off-by: Tim Krüger --- app/build.gradle | 9 +- .../talk/activities/CallActivity.java | 33 +- .../activities/CallNotificationActivity.kt | 41 +-- .../talk/adapters/ParticipantsAdapter.java | 15 +- .../talk/adapters/ReactionsAdapter.kt | 2 +- .../talk/adapters/ReactionsViewHolder.kt | 45 +-- .../talk/adapters/items/AdvancedUserItem.java | 22 +- .../talk/adapters/items/ContactItem.java | 53 ++- .../talk/adapters/items/ConversationItem.kt | 303 ++++++++---------- .../items/MentionAutocompleteItem.java | 34 +- .../talk/adapters/items/MessageResultItem.kt | 17 +- .../talk/adapters/items/ParticipantItem.java | 59 +--- .../IncomingLinkPreviewMessageViewHolder.kt | 29 +- .../IncomingLocationMessageViewHolder.kt | 24 +- .../messages/IncomingPollMessageViewHolder.kt | 29 +- .../IncomingPreviewMessageViewHolder.java | 6 +- .../messages/IncomingTextMessageViewHolder.kt | 30 +- .../IncomingVoiceMessageViewHolder.kt | 15 +- .../talk/adapters/messages/LinkPreview.kt | 11 +- .../OutcomingPreviewMessageViewHolder.java | 4 +- .../messages/PreviewMessageViewHolder.kt | 34 +- .../application/NextcloudTalkApplication.kt | 29 +- .../MentionAutocompleteCallback.java | 2 +- .../talk/controllers/ChatController.kt | 93 +++--- .../controllers/ConversationInfoController.kt | 56 +--- .../ConversationsListController.kt | 123 +++---- .../talk/extensions/ImageViewExtensions.kt | 283 ++++++++++++++++ .../nextcloud/talk/jobs/NotificationWorker.kt | 2 +- .../adapters/PollResultVoterViewHolder.kt | 41 +-- .../PollResultVotersOverviewViewHolder.kt | 59 +--- .../talk/receivers/DirectReplyReceiver.kt | 2 +- .../RemoteFileBrowserItemsListViewHolder.kt | 23 +- .../RemoteFileBrowserItemsViewHolder.kt | 16 +- .../adapters/SharedItemsGridViewHolder.kt | 4 +- .../adapters/SharedItemsListViewHolder.kt | 13 +- .../adapters/SharedItemsViewHolder.kt | 57 +--- .../dialog/ChooseAccountDialogFragment.java | 17 +- .../ChooseAccountShareToDialogFragment.kt | 21 +- .../nextcloud/talk/utils/DisplayUtils.java | 208 +++--------- .../talk/utils/FileSortOrderByName.kt | 5 +- .../nextcloud/talk/utils/FileViewerUtils.kt | 4 +- .../nextcloud/talk/utils/NotificationUtils.kt | 58 ++-- .../utils/OkHttpNetworkFetcherWithCache.java | 41 --- .../com/nextcloud/talk/utils/text/Spans.java | 3 +- .../daveKoeller/AlphanumComparator.java | 2 +- .../parties}/daveKoeller/lgpl-2.1.txt | 0 .../third/parties/fresco/BetterImageSpan.kt | 138 ++++++++ app/src/main/res/drawable/shape_oval.xml | 5 + app/src/main/res/layout/account_item.xml | 8 +- app/src/main/res/layout/activity_main.xml | 3 +- app/src/main/res/layout/call_activity.xml | 113 ++++--- app/src/main/res/layout/call_item.xml | 4 +- .../res/layout/call_notification_activity.xml | 37 ++- .../layout/controller_conversation_info.xml | 6 +- .../main/res/layout/controller_profile.xml | 7 +- .../main/res/layout/controller_settings.xml | 7 +- .../main/res/layout/current_account_item.xml | 8 +- ...m_custom_incoming_link_preview_message.xml | 4 +- .../item_custom_incoming_location_message.xml | 4 +- .../item_custom_incoming_poll_message.xml | 4 +- .../item_custom_incoming_preview_message.xml | 22 +- .../item_custom_incoming_text_message.xml | 4 +- .../item_custom_incoming_voice_message.xml | 4 +- .../item_custom_outcoming_preview_message.xml | 20 +- .../res/layout/poll_result_voter_item.xml | 5 +- app/src/main/res/layout/reaction_item.xml | 5 +- .../res/layout/reference_inside_message.xml | 45 ++- .../main/res/layout/rv_item_browser_file.xml | 5 +- app/src/main/res/layout/rv_item_contact.xml | 8 +- .../res/layout/rv_item_contact_shimmer.xml | 4 +- .../rv_item_conversation_info_participant.xml | 15 +- ...rv_item_conversation_with_last_message.xml | 7 +- app/src/main/res/layout/rv_item_load_more.xml | 3 +- .../res/layout/rv_item_search_message.xml | 7 +- app/src/main/res/layout/shared_item_grid.xml | 8 +- app/src/main/res/layout/shared_item_list.xml | 9 +- app/src/main/res/values/dimens.xml | 4 + app/src/main/res/values/strings.xml | 10 + 78 files changed, 1131 insertions(+), 1379 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt delete mode 100644 app/src/main/java/com/nextcloud/talk/utils/OkHttpNetworkFetcherWithCache.java rename app/src/main/java/{third_parties => third/parties}/daveKoeller/AlphanumComparator.java (99%) rename app/src/main/java/{third_parties => third/parties}/daveKoeller/lgpl-2.1.txt (100%) create mode 100644 app/src/main/java/third/parties/fresco/BetterImageSpan.kt create mode 100644 app/src/main/res/drawable/shape_oval.xml 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