Merge pull request #2449 from nextcloud/bugfix/2376/fresco-blocks-increase-of-minsdkversion

Set minSdkVersion to 23 (Android 6) II
This commit is contained in:
Tim Krüger 2022-12-07 13:46:59 +01:00 committed by GitHub
commit 13f60d1b2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
92 changed files with 1450 additions and 1866 deletions

View File

@ -42,7 +42,7 @@ android {
namespace 'com.nextcloud.talk' namespace 'com.nextcloud.talk'
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 23
targetSdkVersion 31 targetSdkVersion 31
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -245,15 +245,8 @@ dependencies {
implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'com.github.wooplr:Spotlight:1.3' implementation 'com.github.wooplr:Spotlight:1.3'
implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'com.google.code.findbugs:jsr305:3.0.2'
implementation('com.github.nextcloud-deps:ChatKit:0.3.0-1', { implementation 'com.github.nextcloud-deps:ChatKit:0.3.1'
exclude group: 'com.facebook.fresco'
})
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 'joda-time:joda-time:2.12.2'
implementation "io.coil-kt:coil:${coilKtVersion}" implementation "io.coil-kt:coil:${coilKtVersion}"
implementation "io.coil-kt:coil-gif:${coilKtVersion}" implementation "io.coil-kt:coil-gif:${coilKtVersion}"

View File

@ -22,7 +22,6 @@ package com.nextcloud.talk.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.WindowManager import android.view.WindowManager
@ -76,11 +75,9 @@ open class BaseActivity : AppCompatActivity() {
} }
if (appPreferences.isScreenLocked) { if (appPreferences.isScreenLocked) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
SecurityUtils.createKey(appPreferences.screenLockTimeout) SecurityUtils.createKey(appPreferences.screenLockTimeout)
} }
} }
}
fun showCertificateDialog( fun showCertificateDialog(
cert: X509Certificate, cert: X509Certificate,

View File

@ -149,7 +149,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.graphics.drawable.DrawableCompat;
import autodagger.AutoInjector; import autodagger.AutoInjector;
@ -540,20 +539,16 @@ public class CallActivity extends CallBaseActivity {
private void updateAudioOutputButton(WebRtcAudioManager.AudioDevice activeAudioDevice) { private void updateAudioOutputButton(WebRtcAudioManager.AudioDevice activeAudioDevice) {
switch (activeAudioDevice) { switch (activeAudioDevice) {
case BLUETOOTH: case BLUETOOTH:
binding.audioOutputButton.getHierarchy().setPlaceholderImage( binding.audioOutputButton.setImageResource ( R.drawable.ic_baseline_bluetooth_audio_24);
AppCompatResources.getDrawable(context, R.drawable.ic_baseline_bluetooth_audio_24));
break; break;
case SPEAKER_PHONE: case SPEAKER_PHONE:
binding.audioOutputButton.getHierarchy().setPlaceholderImage( binding.audioOutputButton.setImageResource(R.drawable.ic_volume_up_white_24dp);
AppCompatResources.getDrawable(context, R.drawable.ic_volume_up_white_24dp));
break; break;
case EARPIECE: case EARPIECE:
binding.audioOutputButton.getHierarchy().setPlaceholderImage( binding.audioOutputButton.setImageResource(R.drawable.ic_baseline_phone_in_talk_24);
AppCompatResources.getDrawable(context, R.drawable.ic_baseline_phone_in_talk_24));
break; break;
case WIRED_HEADSET: case WIRED_HEADSET:
binding.audioOutputButton.getHierarchy().setPlaceholderImage( binding.audioOutputButton.setImageResource(R.drawable.ic_baseline_headset_mic_24);
AppCompatResources.getDrawable(context, R.drawable.ic_baseline_headset_mic_24));
break; break;
default: default:
Log.e(TAG, "Icon for audio output not available"); Log.e(TAG, "Icon for audio output not available");
@ -737,10 +732,8 @@ public class CallActivity extends CallBaseActivity {
} else { } else {
if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CALL)) { if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CALL)) {
onPermissionsGranted(); onPermissionsGranted();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(PERMISSIONS_CALL, 100);
} else { } else {
onRequestPermissionsResult(100, PERMISSIONS_CALL, new int[]{1, 1}); requestPermissions(PERMISSIONS_CALL, 100);
} }
} }
@ -795,7 +788,7 @@ public class CallActivity extends CallBaseActivity {
onCameraClick(); onCameraClick();
} }
} else { } 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.cameraButton.setAlpha(0.7f);
binding.switchSelfVideoButton.setVisibility(View.GONE); binding.switchSelfVideoButton.setVisibility(View.GONE);
} }
@ -806,7 +799,7 @@ public class CallActivity extends CallBaseActivity {
onMicrophoneClick(); onMicrophoneClick();
} }
} else { } else {
binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); binding.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px);
} }
if (!isConnectionEstablished()) { if (!isConnectionEstablished()) {
@ -917,7 +910,7 @@ public class CallActivity extends CallBaseActivity {
if (!canPublishAudioStream) { if (!canPublishAudioStream) {
microphoneOn = false; 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); toggleMedia(false, false);
} }
@ -961,12 +954,12 @@ public class CallActivity extends CallBaseActivity {
microphoneOn = !microphoneOn; microphoneOn = !microphoneOn;
if (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, updatePictureInPictureActions(R.drawable.ic_mic_white_24px,
getResources().getString(R.string.nc_pip_microphone_mute), getResources().getString(R.string.nc_pip_microphone_mute),
MICROPHONE_PIP_REQUEST_MUTE); MICROPHONE_PIP_REQUEST_MUTE);
} else { } 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, updatePictureInPictureActions(R.drawable.ic_mic_off_white_24px,
getResources().getString(R.string.nc_pip_microphone_unmute), getResources().getString(R.string.nc_pip_microphone_unmute),
MICROPHONE_PIP_REQUEST_UNMUTE); MICROPHONE_PIP_REQUEST_UNMUTE);
@ -974,7 +967,7 @@ public class CallActivity extends CallBaseActivity {
toggleMedia(microphoneOn, false); toggleMedia(microphoneOn, false);
} else { } else {
binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_white_24px); binding.microphoneButton.setImageResource(R.drawable.ic_mic_white_24px);
pulseAnimation.start(); pulseAnimation.start();
toggleMedia(true, false); toggleMedia(true, false);
} }
@ -985,11 +978,7 @@ public class CallActivity extends CallBaseActivity {
R.string.nc_microphone_permission_permanently_denied, R.string.nc_microphone_permission_permanently_denied,
R.string.nc_permissions_settings, (AppCompatActivity) this); R.string.nc_permissions_settings, (AppCompatActivity) this);
} else { } else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(PERMISSIONS_MICROPHONE, 100); requestPermissions(PERMISSIONS_MICROPHONE, 100);
} else {
onRequestPermissionsResult(100, PERMISSIONS_MICROPHONE, new int[]{1});
}
} }
} }
@ -997,7 +986,7 @@ public class CallActivity extends CallBaseActivity {
if (!canPublishVideoStream) { if (!canPublishVideoStream) {
videoOn = false; 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); binding.switchSelfVideoButton.setVisibility(View.GONE);
return; return;
} }
@ -1006,12 +995,12 @@ public class CallActivity extends CallBaseActivity {
videoOn = !videoOn; videoOn = !videoOn;
if (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) { if (cameraEnumerator.getDeviceNames().length > 1) {
binding.switchSelfVideoButton.setVisibility(View.VISIBLE); binding.switchSelfVideoButton.setVisibility(View.VISIBLE);
} }
} else { } 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); binding.switchSelfVideoButton.setVisibility(View.GONE);
} }
@ -1022,12 +1011,7 @@ public class CallActivity extends CallBaseActivity {
R.string.nc_camera_permission_permanently_denied, R.string.nc_camera_permission_permanently_denied,
R.string.nc_permissions_settings, (AppCompatActivity) this); R.string.nc_permissions_settings, (AppCompatActivity) this);
} else { } else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(PERMISSIONS_CAMERA, 100); requestPermissions(PERMISSIONS_CAMERA, 100);
} else {
onRequestPermissionsResult(100, PERMISSIONS_CAMERA, new int[]{1});
}
} }
} }
@ -2675,7 +2659,7 @@ public class CallActivity extends CallBaseActivity {
v.onTouchEvent(event); v.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP && isPushToTalkActive) { if (event.getAction() == MotionEvent.ACTION_UP && isPushToTalkActive) {
isPushToTalkActive = false; 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(); pulseAnimation.stop();
toggleMedia(false, false); toggleMedia(false, false);
animateCallControls(false, 5000); animateCallControls(false, 5000);

View File

@ -28,8 +28,6 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.media.AudioAttributes import android.media.AudioAttributes
import android.media.MediaPlayer import android.media.MediaPlayer
import android.os.Build import android.os.Build
@ -41,24 +39,18 @@ import android.view.View
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import autodagger.AutoInjector 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.R
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.CallNotificationActivityBinding 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.Conversation
import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.models.json.participants.ParticipantsOverall import com.nextcloud.talk.models.json.participants.ParticipantsOverall
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.DoNotDisturbUtils.shouldPlaySound import com.nextcloud.talk.utils.DoNotDisturbUtils.shouldPlaySound
import com.nextcloud.talk.utils.NotificationUtils import com.nextcloud.talk.utils.NotificationUtils
import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri
@ -75,6 +67,7 @@ import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.call_item.*
import okhttp3.Cache import okhttp3.Cache
import org.parceler.Parcels import org.parceler.Parcels
import java.io.IOException import java.io.IOException
@ -356,7 +349,7 @@ class CallNotificationActivity : CallBaseActivity() {
private fun setUpAfterConversationIsKnown() { private fun setUpAfterConversationIsKnown() {
binding!!.conversationNameTextView.text = currentConversation!!.displayName binding!!.conversationNameTextView.text = currentConversation!!.displayName
if (currentConversation!!.type === Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) { if (currentConversation!!.type === Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
setAvatarForOneToOneCall() avatarImageView.loadAvatar(userBeingCalled!!, currentConversation!!.name!!)
} else { } else {
binding!!.avatarImageView.setImageResource(R.drawable.ic_circular_group) binding!!.avatarImageView.setImageResource(R.drawable.ic_circular_group)
} }
@ -364,34 +357,6 @@ class CallNotificationActivity : CallBaseActivity() {
showAnswerControls() 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<CloseableReference<CloseableImage?>>) {
Log.e(TAG, "failed to load avatar")
}
},
UiThreadImmediateExecutorService.getInstance()
)
}
private fun endMediaNotifications() { private fun endMediaNotifications() {
if (mediaPlayer != null) { if (mediaPlayer != null) {
if (mediaPlayer!!.isPlaying) { if (mediaPlayer!!.isPlaying) {

View File

@ -24,12 +24,10 @@ package com.nextcloud.talk.activities
import android.app.KeyguardManager import android.app.KeyguardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.ContactsContract import android.provider.ContactsContract
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi
import autodagger.AutoInjector import autodagger.AutoInjector
import com.bluelinelabs.conductor.Conductor import com.bluelinelabs.conductor.Conductor
import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.Router
@ -148,11 +146,8 @@ class MainActivity : BaseActivity(), ActionBarProvider {
super.onStart() super.onStart()
Log.d(TAG, "onStart: Activity: " + System.identityHashCode(this).toString()) Log.d(TAG, "onStart: Activity: " + System.identityHashCode(this).toString())
logRouterBackStack(router!!) logRouterBackStack(router!!)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
lockScreenIfConditionsApply() lockScreenIfConditionsApply()
} }
}
override fun onResume() { override fun onResume() {
Log.d(TAG, "onResume: Activity: " + System.identityHashCode(this).toString()) Log.d(TAG, "onResume: Activity: " + System.identityHashCode(this).toString())
@ -323,7 +318,6 @@ class MainActivity : BaseActivity(), ActionBarProvider {
}) })
} }
@RequiresApi(api = Build.VERSION_CODES.M)
fun lockScreenIfConditionsApply() { fun lockScreenIfConditionsApply() {
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
if (keyguardManager.isKeyguardSecure && appPreferences.isScreenLocked) { if (keyguardManager.isKeyguardSecure && appPreferences.isScreenLocked) {

View File

@ -12,12 +12,9 @@ import android.widget.ProgressBar;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; 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.R;
import com.nextcloud.talk.activities.CallActivity; 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.MediaStream;
import org.webrtc.MediaStreamTrack; import org.webrtc.MediaStreamTrack;
@ -109,7 +106,7 @@ public class ParticipantsAdapter extends BaseAdapter {
convertView.setLayoutParams(layoutParams); convertView.setLayoutParams(layoutParams);
TextView nickTextView = convertView.findViewById(R.id.peer_nick_text_view); 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(); MediaStream mediaStream = participantDisplayItem.getMediaStream();
if (hasVideoStream(participantDisplayItem, mediaStream)) { if (hasVideoStream(participantDisplayItem, mediaStream)) {
@ -128,13 +125,7 @@ public class ParticipantsAdapter extends BaseAdapter {
nickTextView.setVisibility(View.VISIBLE); nickTextView.setVisibility(View.VISIBLE);
nickTextView.setText(participantDisplayItem.getNick()); nickTextView.setText(participantDisplayItem.getNick());
} }
ImageViewExtensionsKt.loadAvatarWithUrl(imageView,null, participantDisplayItem.getUrlForAvatar());
imageView.setController(null);
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
.setOldController(imageView.getController())
.setImageRequest(DisplayUtils.getImageRequestForUrl(participantDisplayItem.getUrlForAvatar()))
.build();
imageView.setController(draweeController);
} }
ImageView audioOffView = convertView.findViewById(R.id.remote_audio_off); ImageView audioOffView = convertView.findViewById(R.id.remote_audio_off);

View File

@ -34,7 +34,7 @@ class ReactionsAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReactionsViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReactionsViewHolder {
val itemBinding = ReactionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) 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) { override fun onBindViewHolder(holder: ReactionsViewHolder, position: Int) {

View File

@ -22,18 +22,17 @@ package com.nextcloud.talk.adapters
import android.text.TextUtils import android.text.TextUtils
import androidx.recyclerview.widget.RecyclerView 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.R
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication 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.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.models.json.reactions.ReactionVoter
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
class ReactionsViewHolder( class ReactionsViewHolder(
private val binding: ReactionItemBinding, private val binding: ReactionItemBinding,
private val baseUrl: String? private val user: User?
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(reactionItem: ReactionItem, clickListener: ReactionItemClickListener) { fun bind(reactionItem: ReactionItem, clickListener: ReactionItemClickListener) {
@ -41,7 +40,7 @@ class ReactionsViewHolder(
binding.reaction.text = reactionItem.reaction binding.reaction.text = reactionItem.reaction
binding.name.text = reactionItem.reactionVoter.actorDisplayName binding.name.text = reactionItem.reactionVoter.actorDisplayName
if (baseUrl != null && baseUrl.isNotEmpty()) { if (user != null && user.baseUrl?.isNotEmpty() == true) {
loadAvatar(reactionItem) loadAvatar(reactionItem)
} }
} }
@ -52,35 +51,13 @@ class ReactionsViewHolder(
if (!TextUtils.isEmpty(reactionItem.reactionVoter.actorDisplayName)) { if (!TextUtils.isEmpty(reactionItem.reactionVoter.actorDisplayName)) {
displayName = reactionItem.reactionVoter.actorDisplayName!! displayName = reactionItem.reactionVoter.actorDisplayName!!
} }
val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() binding.avatar.loadGuestAvatar(user!!.baseUrl!!, displayName!!, false)
.setOldController(binding.avatar.controller)
.setAutoPlayAnimations(true)
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForGuestAvatar(
baseUrl,
displayName,
false
)
)
)
.build()
binding.avatar.controller = draweeController
} else if (reactionItem.reactionVoter.actorType == ReactionVoter.ReactionActorType.USERS) { } else if (reactionItem.reactionVoter.actorType == ReactionVoter.ReactionActorType.USERS) {
val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() binding.avatar.loadAvatar(
.setOldController(binding.avatar.controller) user!!,
.setAutoPlayAnimations(true) reactionItem.reactionVoter.actorId!!,
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar(
baseUrl,
reactionItem.reactionVoter.actorId,
false false
) )
)
)
.build()
binding.avatar.controller = draweeController
} }
} }
} }

View File

@ -27,15 +27,12 @@ import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; 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.R;
import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.databinding.AccountItemBinding; import com.nextcloud.talk.databinding.AccountItemBinding;
import com.nextcloud.talk.extensions.ImageViewExtensionsKt;
import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.ui.theme.ViewThemeUtils; 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.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -108,7 +105,6 @@ public class AdvancedUserItem extends AbstractFlexibleItem<AdvancedUserItem.User
@Override @Override
public void bindViewHolder(FlexibleAdapter adapter, UserItemViewHolder holder, int position, List payloads) { public void bindViewHolder(FlexibleAdapter adapter, UserItemViewHolder holder, int position, List payloads) {
holder.binding.userIcon.setController(null);
if (adapter.hasFilter()) { if (adapter.hasFilter()) {
FlexibleUtils.highlightText( FlexibleUtils.highlightText(
@ -129,24 +125,10 @@ public class AdvancedUserItem extends AbstractFlexibleItem<AdvancedUserItem.User
} }
} }
holder.binding.userIcon.getHierarchy().setPlaceholderImage(R.drawable.account_circle_48dp);
holder.binding.userIcon.getHierarchy().setFailureImage(R.drawable.account_circle_48dp);
if (user != null && user.getBaseUrl() != null && if (user != null && user.getBaseUrl() != null &&
user.getBaseUrl().startsWith("http://") || user.getBaseUrl().startsWith("http://") ||
user.getBaseUrl().startsWith("https://")) { user.getBaseUrl().startsWith("https://")) {
ImageViewExtensionsKt.loadAvatar(holder.binding.userIcon, user, participant.getCalculatedActorId(), true);
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
.setOldController(holder.binding.userIcon.getController())
.setAutoPlayAnimations(true)
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar(
user.getBaseUrl(),
participant.getCalculatedActorId(),
true)))
.build();
holder.binding.userIcon.setController(draweeController);
} }
} }

View File

@ -29,16 +29,13 @@ import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; 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.R;
import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.databinding.RvItemContactBinding; import com.nextcloud.talk.databinding.RvItemContactBinding;
import com.nextcloud.talk.extensions.ImageViewExtensionsKt;
import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.ui.theme.ViewThemeUtils; 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.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -110,7 +107,6 @@ public class ContactItem extends AbstractFlexibleItem<ContactItem.ContactItemVie
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@Override @Override
public void bindViewHolder(FlexibleAdapter adapter, ContactItemViewHolder holder, int position, List payloads) { public void bindViewHolder(FlexibleAdapter adapter, ContactItemViewHolder holder, int position, List payloads) {
holder.binding.avatarDraweeView.setController(null);
if (participant.getSelected()) { if (participant.getSelected()) {
viewThemeUtils.platform.colorImageView(holder.binding.checkedImageView); viewThemeUtils.platform.colorImageView(holder.binding.checkedImageView);
@ -125,14 +121,14 @@ public class ContactItem extends AbstractFlexibleItem<ContactItem.ContactItemVie
R.color.medium_emphasis_text, R.color.medium_emphasis_text,
null) null)
); );
holder.binding.avatarDraweeView.setAlpha(0.38f); holder.binding.avatarView.setAlpha(0.38f);
} else { } else {
holder.binding.nameText.setTextColor(ResourcesCompat.getColor( holder.binding.nameText.setTextColor(ResourcesCompat.getColor(
holder.binding.nameText.getContext().getResources(), holder.binding.nameText.getContext().getResources(),
R.color.high_emphasis_text, R.color.high_emphasis_text,
null) null)
); );
holder.binding.avatarDraweeView.setAlpha(1.0f); holder.binding.avatarView.setAlpha(1.0f);
} }
holder.binding.nameText.setText(participant.getDisplayName()); holder.binding.nameText.setText(participant.getDisplayName());
@ -179,38 +175,29 @@ public class ContactItem extends AbstractFlexibleItem<ContactItem.ContactItemVie
.getResources().getString(R.string.nc_guest); .getResources().getString(R.string.nc_guest);
} }
setUserStyleAvatar(holder, ImageViewExtensionsKt.loadAvatar(holder.binding.avatarView, user, displayName, true);
ApiUtils.getUrlForGuestAvatar(user.getBaseUrl(), displayName, false));
} else if (participant.getCalculatedActorType() == Participant.ActorType.USERS || } else if (participant.getCalculatedActorType() == Participant.ActorType.USERS ||
PARTICIPANT_SOURCE_USERS.equals(participant.getSource())) { PARTICIPANT_SOURCE_USERS.equals(participant.getSource())) {
setUserStyleAvatar(holder, ImageViewExtensionsKt.loadAvatar(holder.binding.avatarView, user, participant.getCalculatedActorId(), true);
ApiUtils.getUrlForAvatar(user.getBaseUrl(),
participant.getCalculatedActorId(),
false));
} }
} }
private void setUserStyleAvatar(ContactItemViewHolder holder, String avatarUrl) {
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
.setOldController(holder.binding.avatarDraweeView.getController())
.setAutoPlayAnimations(true)
.setImageRequest(DisplayUtils.getImageRequestForUrl(avatarUrl))
.build();
holder.binding.avatarDraweeView.setController(draweeController);
}
private void setGenericAvatar( private void setGenericAvatar(
ContactItemViewHolder holder, ContactItemViewHolder holder,
int roundPlaceholderDrawable, int roundPlaceholderDrawable,
int fallbackImageResource) { int fallbackImageResource) {
Object avatar;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
holder.binding.avatarDraweeView.getHierarchy().setPlaceholderImage( avatar = viewThemeUtils.talk.themePlaceholderAvatar(
DisplayUtils.getRoundedDrawable( holder.binding.avatarView,
viewThemeUtils.talk.themePlaceholderAvatar(holder.binding.avatarDraweeView, roundPlaceholderDrawable
roundPlaceholderDrawable))); );
} else { } else {
holder.binding.avatarDraweeView.setImageResource(fallbackImageResource); avatar = fallbackImageResource;
} }
ImageViewExtensionsKt.loadAvatar(holder.binding.avatarView, avatar);
} }
@Override @Override

View File

@ -1,367 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Andy Scherzinger
* @author Marcel Hibbe
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.adapters.items;
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 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.RvItemConversationWithLastMessageBinding;
import com.nextcloud.talk.models.json.chat.ChatMessage;
import com.nextcloud.talk.models.json.conversations.Conversation;
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;
import java.util.List;
import java.util.regex.Pattern;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import eu.davidea.flexibleadapter.items.IFilterable;
import eu.davidea.flexibleadapter.items.IFlexible;
import eu.davidea.flexibleadapter.items.ISectionable;
import eu.davidea.viewholders.FlexibleViewHolder;
public class ConversationItem extends AbstractFlexibleItem<ConversationItem.ConversationItemViewHolder> implements
ISectionable<ConversationItem.ConversationItemViewHolder, GenericTextHeaderItem>, IFilterable<String> {
public static final int VIEW_TYPE = R.layout.rv_item_conversation_with_last_message;
private static final float STATUS_SIZE_IN_DP = 9f;
private final Conversation conversation;
private final User user;
private final Context context;
private GenericTextHeaderItem header;
private final ViewThemeUtils viewThemeUtils;
public ConversationItem(Conversation conversation, User user, Context activityContext, final ViewThemeUtils viewThemeUtils) {
this.conversation = conversation;
this.user = user;
this.context = activityContext;
this.viewThemeUtils = viewThemeUtils;
}
public ConversationItem(Conversation conversation, User user,
Context activityContext, GenericTextHeaderItem genericTextHeaderItem,
final ViewThemeUtils viewThemeUtils) {
this(conversation, user, activityContext, viewThemeUtils);
this.header = genericTextHeaderItem;
}
@Override
public boolean equals(Object o) {
if (o instanceof ConversationItem) {
ConversationItem inItem = (ConversationItem) o;
return conversation.equals(inItem.getModel());
}
return false;
}
public Conversation getModel() {
return conversation;
}
@Override
public int hashCode() {
int result = conversation.hashCode();
result = 31 * result;
return result;
}
@Override
public int getLayoutRes() {
return R.layout.rv_item_conversation_with_last_message;
}
@Override
public int getItemViewType() {
return VIEW_TYPE;
}
@Override
public ConversationItemViewHolder createViewHolder(View view, FlexibleAdapter<IFlexible> adapter) {
return new ConversationItemViewHolder(view, adapter);
}
@SuppressLint("SetTextI18n")
@Override
public void bindViewHolder(FlexibleAdapter<IFlexible> adapter,
ConversationItemViewHolder holder,
int position,
List<Object> payloads) {
Context appContext =
NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext();
holder.binding.dialogAvatar.setController(null);
holder.binding.dialogName.setTextColor(ResourcesCompat.getColor(context.getResources(),
R.color.conversation_item_header,
null));
if (adapter.hasFilter()) {
viewThemeUtils.platform.highlightText(holder.binding.dialogName,
conversation.getDisplayName(),
String.valueOf(adapter.getFilter(String.class)));
} else {
holder.binding.dialogName.setText(conversation.getDisplayName());
}
if (conversation.getUnreadMessages() > 0) {
holder.binding.dialogName.setTypeface(holder.binding.dialogName.getTypeface(), Typeface.BOLD);
holder.binding.dialogLastMessage.setTypeface(holder.binding.dialogLastMessage.getTypeface(), Typeface.BOLD);
holder.binding.dialogUnreadBubble.setVisibility(View.VISIBLE);
if (conversation.getUnreadMessages() < 1000) {
holder.binding.dialogUnreadBubble.setText(Long.toString(conversation.getUnreadMessages()));
} else {
holder.binding.dialogUnreadBubble.setText(R.string.tooManyUnreadMessages);
}
ColorStateList lightBubbleFillColor = ColorStateList.valueOf(
ContextCompat.getColor(context,
R.color.conversation_unread_bubble));
int lightBubbleTextColor = ContextCompat.getColor(
context,
R.color.conversation_unread_bubble_text);
if (conversation.getType() == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble);
} else if (conversation.getUnreadMention()) {
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "direct-mention-flag")) {
if (conversation.getUnreadMentionDirect()) {
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.setChipBackgroundColor(lightBubbleFillColor);
holder.binding.dialogUnreadBubble.setTextColor(lightBubbleTextColor);
}
} else {
holder.binding.dialogName.setTypeface(null, Typeface.NORMAL);
holder.binding.dialogDate.setTypeface(null, Typeface.NORMAL);
holder.binding.dialogLastMessage.setTypeface(null, Typeface.NORMAL);
holder.binding.dialogUnreadBubble.setVisibility(View.GONE);
}
if (conversation.getFavorite()) {
holder.binding.favoriteConversationImageView.setVisibility(View.VISIBLE);
} else {
holder.binding.favoriteConversationImageView.setVisibility(View.GONE);
}
if (conversation.getStatus() != null && Conversation.ConversationType.ROOM_SYSTEM != conversation.getType()) {
float size = DisplayUtils.convertDpToPixel(STATUS_SIZE_IN_DP, appContext);
holder.binding.userStatusImage.setVisibility(View.VISIBLE);
holder.binding.userStatusImage.setImageDrawable(new StatusDrawable(
conversation.getStatus(),
conversation.getStatusIcon(),
size,
context.getResources().getColor(R.color.bg_default),
appContext));
} else {
holder.binding.userStatusImage.setVisibility(View.GONE);
}
if (conversation.getLastMessage() != null) {
holder.binding.dialogDate.setVisibility(View.VISIBLE);
holder.binding.dialogDate.setText(
DateUtils.getRelativeTimeSpanString(conversation.getLastActivity() * 1000L,
System.currentTimeMillis(),
0,
DateUtils.FORMAT_ABBREV_RELATIVE));
if (!TextUtils.isEmpty(conversation.getLastMessage().getSystemMessage()) ||
Conversation.ConversationType.ROOM_SYSTEM == conversation.getType()) {
holder.binding.dialogLastMessage.setText(conversation.getLastMessage().getText());
} else {
String authorDisplayName = "";
conversation.getLastMessage().setActiveUser(user);
String text;
if (conversation.getLastMessage().getCalculateMessageType() == ChatMessage.MessageType.REGULAR_TEXT_MESSAGE) {
if (conversation.getLastMessage().getActorId().equals(user.getUserId())) {
text = String.format(appContext.getString(R.string.nc_formatted_message_you),
conversation.getLastMessage().getLastMessageDisplayText());
} else {
authorDisplayName = !TextUtils.isEmpty(conversation.getLastMessage().getActorDisplayName()) ?
conversation.getLastMessage().getActorDisplayName() :
"guests".equals(conversation.getLastMessage().getActorType()) ?
appContext.getString(R.string.nc_guest) : "";
text = String.format(appContext.getString(R.string.nc_formatted_message),
authorDisplayName,
conversation.getLastMessage().getLastMessageDisplayText());
}
} else {
text = conversation.getLastMessage().getLastMessageDisplayText();
}
holder.binding.dialogLastMessage.setText(text);
}
} else {
holder.binding.dialogDate.setVisibility(View.GONE);
holder.binding.dialogLastMessage.setText(R.string.nc_no_messages_yet);
}
holder.binding.dialogAvatar.setVisibility(View.VISIBLE);
boolean shouldLoadAvatar = true;
String objectType;
if (!TextUtils.isEmpty(objectType = conversation.getObjectType())) {
switch (objectType) {
case "share:password":
shouldLoadAvatar = false;
holder.binding.dialogAvatar.setImageDrawable(
ContextCompat.getDrawable(context,
R.drawable.ic_circular_lock));
break;
case "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));
}
break;
default:
break;
}
}
if (conversation.getType() == Conversation.ConversationType.ROOM_SYSTEM) {
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);
holder.binding.dialogAvatar.getHierarchy().setPlaceholderImage(
DisplayUtils.getRoundedDrawable(layerDrawable));
} else {
holder.binding.dialogAvatar.getHierarchy().setPlaceholderImage(R.mipmap.ic_launcher);
}
shouldLoadAvatar = false;
}
if (shouldLoadAvatar) {
switch (conversation.getType()) {
case ROOM_TYPE_ONE_TO_ONE_CALL:
if (!TextUtils.isEmpty(conversation.getName())) {
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
.setOldController(holder.binding.dialogAvatar.getController())
.setAutoPlayAnimations(true)
.setImageRequest(DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar(user.getBaseUrl(),
conversation.getName(),
true),
user))
.build();
holder.binding.dialogAvatar.setController(draweeController);
} else {
holder.binding.dialogAvatar.setVisibility(View.GONE);
}
break;
case 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));
}
break;
case 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));
}
break;
default:
holder.binding.dialogAvatar.setVisibility(View.GONE);
}
}
}
@Override
public boolean filter(String constraint) {
return conversation.getDisplayName() != null &&
Pattern
.compile(constraint, Pattern.CASE_INSENSITIVE | Pattern.LITERAL)
.matcher(conversation.getDisplayName().trim())
.find();
}
@Override
public GenericTextHeaderItem getHeader() {
return header;
}
@Override
public void setHeader(GenericTextHeaderItem header) {
this.header = header;
}
static class ConversationItemViewHolder extends FlexibleViewHolder {
RvItemConversationWithLastMessageBinding binding;
ConversationItemViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter);
binding = RvItemConversationWithLastMessageBinding.bind(view);
}
}
}

View File

@ -0,0 +1,349 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Andy Scherzinger
* @author Marcel Hibbe
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.adapters.items
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Typeface
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.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.DisplayUtils
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFilterable
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.flexibleadapter.items.ISectionable
import eu.davidea.viewholders.FlexibleViewHolder
import java.util.regex.Pattern
class ConversationItem(
val model: Conversation,
private val user: User,
private val context: Context,
private val viewThemeUtils: ViewThemeUtils
) : AbstractFlexibleItem<ConversationItemViewHolder>(),
ISectionable<ConversationItemViewHolder, GenericTextHeaderItem?>,
IFilterable<String?> {
private var header: GenericTextHeaderItem? = null
constructor(
conversation: Conversation,
user: User,
activityContext: Context,
genericTextHeaderItem: GenericTextHeaderItem?,
viewThemeUtils: ViewThemeUtils
) : this(conversation, user, activityContext, viewThemeUtils) {
header = genericTextHeaderItem
}
override fun equals(other: Any?): Boolean {
if (other is ConversationItem) {
return model == other.model
}
return false
}
override fun hashCode(): Int {
var result = model.hashCode()
result *= 31
return result
}
override fun getLayoutRes(): Int {
return R.layout.rv_item_conversation_with_last_message
}
override fun getItemViewType(): Int {
return VIEW_TYPE
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>?>?): ConversationItemViewHolder {
return ConversationItemViewHolder(view, adapter)
}
@SuppressLint("SetTextI18n")
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<*>?>,
holder: ConversationItemViewHolder,
position: Int,
payloads: List<Any>
) {
val appContext = sharedApplication!!.applicationContext
holder.binding.dialogName.setTextColor(
ResourcesCompat.getColor(
context.resources,
R.color.conversation_item_header,
null
)
)
if (adapter.hasFilter()) {
viewThemeUtils.platform.highlightText(
holder.binding.dialogName,
model.displayName!!, adapter.getFilter(String::class.java).toString()
)
} else {
holder.binding.dialogName.text = model.displayName
}
if (model.unreadMessages > 0) {
showUnreadMessages(holder)
} else {
holder.binding.dialogName.setTypeface(null, Typeface.NORMAL)
holder.binding.dialogDate.setTypeface(null, Typeface.NORMAL)
holder.binding.dialogLastMessage.setTypeface(null, Typeface.NORMAL)
holder.binding.dialogUnreadBubble.visibility = View.GONE
}
if (model.favorite) {
holder.binding.favoriteConversationImageView.visibility = View.VISIBLE
} else {
holder.binding.favoriteConversationImageView.visibility = View.GONE
}
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.statusIcon,
size,
context.resources.getColor(R.color.bg_default),
appContext
)
)
} 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 * MILLIES,
System.currentTimeMillis(),
0,
DateUtils.FORMAT_ABBREV_RELATIVE
)
if (!TextUtils.isEmpty(model.lastMessage!!.systemMessage) ||
ConversationType.ROOM_SYSTEM === model.type
) {
holder.binding.dialogLastMessage.text = model.lastMessage!!.text
} else {
model.lastMessage!!.activeUser = user
val text = if (model.lastMessage!!.getCalculateMessageType() === ChatMessage.MessageType
.REGULAR_TEXT_MESSAGE
) {
calculateRegularLastMessageText(appContext)
} else {
model.lastMessage!!.lastMessageDisplayText
}
holder.binding.dialogLastMessage.text = text
}
} else {
holder.binding.dialogDate.visibility = View.GONE
holder.binding.dialogLastMessage.setText(R.string.nc_no_messages_yet)
}
}
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
)
}
} else {
viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble)
}
} else {
holder.binding.dialogUnreadBubble.chipBackgroundColor = lightBubbleFillColor
holder.binding.dialogUnreadBubble.setTextColor(lightBubbleTextColor)
}
}
override fun filter(constraint: String?): Boolean {
return model.displayName != null &&
Pattern
.compile(constraint!!, Pattern.CASE_INSENSITIVE or Pattern.LITERAL)
.matcher(model.displayName!!.trim { it <= ' ' })
.find()
}
override fun getHeader(): GenericTextHeaderItem? {
return header
}
override fun setHeader(header: GenericTextHeaderItem?) {
this.header = header
}
class ConversationItemViewHolder(view: View?, adapter: FlexibleAdapter<*>?) : FlexibleViewHolder(view, adapter) {
var binding: RvItemConversationWithLastMessageBinding
init {
binding = RvItemConversationWithLastMessageBinding.bind(view!!)
}
}
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
}
}

View File

@ -29,15 +29,13 @@ import android.content.Context;
import android.os.Build; import android.os.Build;
import android.view.View; 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.R;
import com.nextcloud.talk.data.user.model.User; 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.mention.Mention;
import com.nextcloud.talk.models.json.status.StatusType; import com.nextcloud.talk.models.json.status.StatusType;
import com.nextcloud.talk.ui.StatusDrawable; import com.nextcloud.talk.ui.StatusDrawable;
import com.nextcloud.talk.ui.theme.ViewThemeUtils; import com.nextcloud.talk.ui.theme.ViewThemeUtils;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.DisplayUtils; import com.nextcloud.talk.utils.DisplayUtils;
import java.util.List; import java.util.List;
@ -151,34 +149,22 @@ public class MentionAutocompleteItem extends AbstractFlexibleItem<ParticipantIte
if (SOURCE_CALLS.equals(source)) { if (SOURCE_CALLS.equals(source)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
holder.binding.avatarDraweeView.getHierarchy().setPlaceholderImage( ImageViewExtensionsKt.loadAvatar(
DisplayUtils.getRoundedDrawable( holder.binding.avatarView,
viewThemeUtils.talk.themePlaceholderAvatar(holder.binding.avatarDraweeView, viewThemeUtils.talk.themePlaceholderAvatar(
R.drawable.ic_avatar_group))); holder.binding.avatarView,
R.drawable.ic_avatar_group
)
);
} else { } else {
holder.binding.avatarDraweeView.setImageResource(R.drawable.ic_circular_group); ImageViewExtensionsKt.loadAvatar(holder.binding.avatarView, R.drawable.ic_circular_group);
} }
} else { } else {
String avatarId = objectId; String avatarId = objectId;
String avatarUrl = ApiUtils.getUrlForAvatar(currentUser.getBaseUrl(),
avatarId, true);
if (SOURCE_GUESTS.equals(source)) { if (SOURCE_GUESTS.equals(source)) {
avatarId = displayName; avatarId = displayName;
avatarUrl = ApiUtils.getUrlForGuestAvatar(
currentUser.getBaseUrl(),
avatarId,
false);
} }
ImageViewExtensionsKt.loadAvatar(holder.binding.avatarView, currentUser, avatarId, true);
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);
} }
drawStatus(holder); drawStatus(holder);

View File

@ -2,6 +2,8 @@
* Nextcloud Talk application * Nextcloud Talk application
* *
* @author Álvaro Brey * @author Álvaro Brey
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2022 Álvaro Brey * Copyright (C) 2022 Álvaro Brey
* Copyright (C) 2022 Nextcloud GmbH * Copyright (C) 2022 Nextcloud GmbH
* *
@ -27,9 +29,9 @@ import androidx.recyclerview.widget.RecyclerView
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.RvItemSearchMessageBinding import com.nextcloud.talk.databinding.RvItemSearchMessageBinding
import com.nextcloud.talk.extensions.loadThumbnail
import com.nextcloud.talk.models.domain.SearchMessageEntry import com.nextcloud.talk.models.domain.SearchMessageEntry
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.DisplayUtils
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFilterable import eu.davidea.flexibleadapter.items.IFilterable
@ -72,7 +74,7 @@ data class MessageResultItem constructor(
) { ) {
holder.binding.conversationTitle.text = messageEntry.title holder.binding.conversationTitle.text = messageEntry.title
bindMessageExcerpt(holder) bindMessageExcerpt(holder)
loadImage(holder) messageEntry.thumbnailURL?.let { holder.binding.thumbnail.loadThumbnail(it, currentUser) }
} }
private fun bindMessageExcerpt(holder: ViewHolder) { 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 filter(constraint: String?): Boolean = true
override fun getItemViewType(): Int { override fun getItemViewType(): Int {

View File

@ -27,23 +27,20 @@ package com.nextcloud.talk.adapters.items;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; 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.R;
import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.databinding.RvItemConversationInfoParticipantBinding; 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.converters.EnumParticipantTypeConverter;
import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.models.json.participants.Participant.InCallFlags; import com.nextcloud.talk.models.json.participants.Participant.InCallFlags;
import com.nextcloud.talk.models.json.status.StatusType; import com.nextcloud.talk.models.json.status.StatusType;
import com.nextcloud.talk.ui.StatusDrawable; import com.nextcloud.talk.ui.StatusDrawable;
import com.nextcloud.talk.ui.theme.ViewThemeUtils; import com.nextcloud.talk.ui.theme.ViewThemeUtils;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.DisplayUtils; import com.nextcloud.talk.utils.DisplayUtils;
import java.util.List; import java.util.List;
@ -111,8 +108,6 @@ public class ParticipantItem extends AbstractFlexibleItem<ParticipantItem.Partic
@Override @Override
public void bindViewHolder(FlexibleAdapter adapter, ParticipantItemViewHolder holder, int position, List payloads) { public void bindViewHolder(FlexibleAdapter adapter, ParticipantItemViewHolder holder, int position, List payloads) {
holder.binding.avatarDraweeView.setController(null);
drawStatus(holder); drawStatus(holder);
if (!isOnline) { if (!isOnline) {
@ -121,14 +116,14 @@ public class ParticipantItem extends AbstractFlexibleItem<ParticipantItem.Partic
R.color.medium_emphasis_text, R.color.medium_emphasis_text,
null) null)
); );
holder.binding.avatarDraweeView.setAlpha(0.38f); holder.binding.avatarView.setAlpha(0.38f);
} else { } else {
holder.binding.nameText.setTextColor(ResourcesCompat.getColor( holder.binding.nameText.setTextColor(ResourcesCompat.getColor(
holder.binding.nameText.getContext().getResources(), holder.binding.nameText.getContext().getResources(),
R.color.high_emphasis_text, R.color.high_emphasis_text,
null) null)
); );
holder.binding.avatarDraweeView.setAlpha(1.0f); holder.binding.avatarView.setAlpha(1.0f);
} }
holder.binding.nameText.setText(participant.getDisplayName()); holder.binding.nameText.setText(participant.getDisplayName());
@ -152,23 +147,9 @@ public class ParticipantItem extends AbstractFlexibleItem<ParticipantItem.Partic
"groups".equals(participant.getSource()) || "groups".equals(participant.getSource()) ||
participant.getCalculatedActorType() == Participant.ActorType.CIRCLES || participant.getCalculatedActorType() == Participant.ActorType.CIRCLES ||
"circles".equals(participant.getSource())) { "circles".equals(participant.getSource())) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ImageViewExtensionsKt.loadGroupCallAvatar(holder.binding.avatarView, viewThemeUtils);
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);
}
} else if (participant.getCalculatedActorType() == Participant.ActorType.EMAILS) { } else if (participant.getCalculatedActorType() == Participant.ActorType.EMAILS) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ImageViewExtensionsKt.loadMailAvatar(holder.binding.avatarView, viewThemeUtils);
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);
}
} else if (participant.getCalculatedActorType() == Participant.ActorType.GUESTS || } else if (participant.getCalculatedActorType() == Participant.ActorType.GUESTS ||
participant.getType() == Participant.ParticipantType.GUEST || participant.getType() == Participant.ParticipantType.GUEST ||
participant.getType() == Participant.ParticipantType.GUEST_MODERATOR) { participant.getType() == Participant.ParticipantType.GUEST_MODERATOR) {
@ -180,25 +161,11 @@ public class ParticipantItem extends AbstractFlexibleItem<ParticipantItem.Partic
displayName = participant.getDisplayName(); displayName = participant.getDisplayName();
} }
DraweeController draweeController = Fresco.newDraweeControllerBuilder() ImageViewExtensionsKt.loadGuestAvatar(holder.binding.avatarView, user, displayName, false);
.setOldController(holder.binding.avatarDraweeView.getController())
.setAutoPlayAnimations(true)
.setImageRequest(DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForGuestAvatar(user.getBaseUrl(),
displayName, false)))
.build();
holder.binding.avatarDraweeView.setController(draweeController);
} else if (participant.getCalculatedActorType() == Participant.ActorType.USERS || } else if (participant.getCalculatedActorType() == Participant.ActorType.USERS ||
participant.getSource().equals("users")) { participant.getSource().equals("users")) {
DraweeController draweeController = Fresco.newDraweeControllerBuilder() ImageViewExtensionsKt.loadAvatar(holder.binding.avatarView, user, participant.getCalculatedActorId(), true);
.setOldController(holder.binding.avatarDraweeView.getController())
.setAutoPlayAnimations(true)
.setImageRequest(DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar(user.getBaseUrl(),
participant.getCalculatedActorId(), false)))
.build();
holder.binding.avatarDraweeView.setController(draweeController);
} }
Resources resources = NextcloudTalkApplication.Companion.getSharedApplication().getResources(); Resources resources = NextcloudTalkApplication.Companion.getSharedApplication().getResources();

View File

@ -24,25 +24,21 @@ package com.nextcloud.talk.adapters.messages
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.os.Build
import android.text.TextUtils import android.text.TextUtils
import android.view.View import android.view.View
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import autodagger.AutoInjector import autodagger.AutoInjector
import coil.load import coil.load
import com.amulyakhare.textdrawable.TextDrawable
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingLinkPreviewMessageBinding import com.nextcloud.talk.databinding.ItemCustomIncomingLinkPreviewMessageBinding
import com.nextcloud.talk.extensions.loadBotsAvatar
import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders import com.stfalcon.chatkit.messages.MessageHolders
import javax.inject.Inject import javax.inject.Inject
@ -143,26 +139,9 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) : M
if (message.actorType == "guests") { if (message.actorType == "guests") {
// do nothing, avatar is set // do nothing, avatar is set
} else if (message.actorType == "bots" && message.actorId == "changelog") { } else if (message.actorType == "bots" && message.actorId == "changelog") {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { binding.messageUserAvatar.loadChangelogBotAvatar()
val layers = arrayOfNulls<Drawable>(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)
}
} else if (message.actorType == "bots") { } else if (message.actorType == "bots") {
val drawable = TextDrawable.builder() binding.messageUserAvatar.loadBotsAvatar()
.beginConfig()
.bold()
.endConfig()
.buildRound(
">",
ResourcesCompat.getColor(context.resources, R.color.black, null)
)
binding.messageUserAvatar.visibility = View.VISIBLE
binding.messageUserAvatar.setImageDrawable(drawable)
} }
} }

View File

@ -29,8 +29,6 @@ package com.nextcloud.talk.adapters.messages
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.net.Uri import android.net.Uri
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
@ -40,18 +38,17 @@ import android.view.View
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.content.res.AppCompatResources
import autodagger.AutoInjector import autodagger.AutoInjector
import coil.load import coil.load
import com.amulyakhare.textdrawable.TextDrawable
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingLocationMessageBinding 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.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.UriUtils import com.nextcloud.talk.utils.UriUtils
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders import com.stfalcon.chatkit.messages.MessageHolders
@ -136,22 +133,9 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : Mess
if (message.actorType == "guests") { if (message.actorType == "guests") {
// do nothing, avatar is set // do nothing, avatar is set
} else if (message.actorType == "bots" && message.actorId == "changelog") { } else if (message.actorType == "bots" && message.actorId == "changelog") {
val layers = arrayOfNulls<Drawable>(2) binding.messageUserAvatar.loadChangelogBotAvatar()
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))
} else if (message.actorType == "bots") { } else if (message.actorType == "bots") {
val drawable = TextDrawable.builder() binding.messageUserAvatar.loadBotsAvatar()
.beginConfig()
.bold()
.endConfig()
.buildRound(
">",
context!!.resources.getColor(R.color.black)
)
binding.messageUserAvatar.visibility = View.VISIBLE
binding.messageUserAvatar.setImageDrawable(drawable)
} }
} else { } else {
if (message.isOneToOneConversation) { if (message.isOneToOneConversation) {

View File

@ -22,27 +22,23 @@ package com.nextcloud.talk.adapters.messages
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.os.Build
import android.text.TextUtils import android.text.TextUtils
import android.view.View import android.view.View
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import autodagger.AutoInjector import autodagger.AutoInjector
import coil.load import coil.load
import com.amulyakhare.textdrawable.TextDrawable
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.activities.MainActivity import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingPollMessageBinding 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.models.json.chat.ChatMessage
import com.nextcloud.talk.polls.ui.PollMainDialogFragment import com.nextcloud.talk.polls.ui.PollMainDialogFragment
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders import com.stfalcon.chatkit.messages.MessageHolders
import javax.inject.Inject import javax.inject.Inject
@ -170,26 +166,9 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : MessageH
if (message.actorType == "guests") { if (message.actorType == "guests") {
// do nothing, avatar is set // do nothing, avatar is set
} else if (message.actorType == "bots" && message.actorId == "changelog") { } else if (message.actorType == "bots" && message.actorId == "changelog") {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { binding.messageUserAvatar.loadChangelogBotAvatar()
val layers = arrayOfNulls<Drawable>(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)
}
} else if (message.actorType == "bots") { } else if (message.actorType == "bots") {
val drawable = TextDrawable.builder() binding.messageUserAvatar.loadBotsAvatar()
.beginConfig()
.bold()
.endConfig()
.buildRound(
">",
ResourcesCompat.getColor(context.resources, R.color.black, null)
)
binding.messageUserAvatar.visibility = View.VISIBLE
binding.messageUserAvatar.setImageDrawable(drawable)
} }
} }

View File

@ -3,7 +3,7 @@
* *
* @author Andy Scherzinger * @author Andy Scherzinger
* @author Tim Krüger * @author Tim Krüger
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me> * Copyright (C) 2021-2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -23,9 +23,9 @@
package com.nextcloud.talk.adapters.messages; package com.nextcloud.talk.adapters.messages;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import com.facebook.drawee.view.SimpleDraweeView;
import com.google.android.material.card.MaterialCardView; import com.google.android.material.card.MaterialCardView;
import com.nextcloud.talk.R; import com.nextcloud.talk.R;
import com.nextcloud.talk.databinding.ItemCustomIncomingPreviewMessageBinding; import com.nextcloud.talk.databinding.ItemCustomIncomingPreviewMessageBinding;
@ -74,7 +74,7 @@ public class IncomingPreviewMessageViewHolder extends PreviewMessageViewHolder {
} }
@Override @Override
public SimpleDraweeView getPreviewContactPhoto() { public ImageView getPreviewContactPhoto() {
return binding.contactPhoto; return binding.contactPhoto;
} }

View File

@ -26,24 +26,21 @@ package com.nextcloud.talk.adapters.messages
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.net.Uri import android.net.Uri
import android.os.Build
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString import android.text.SpannableString
import android.text.TextUtils import android.text.TextUtils
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import autodagger.AutoInjector import autodagger.AutoInjector
import coil.load import coil.load
import com.amulyakhare.textdrawable.TextDrawable
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding 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.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
@ -179,28 +176,9 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : MessageHolde
if (message.actorType == "guests") { if (message.actorType == "guests") {
// do nothing, avatar is set // do nothing, avatar is set
} else if (message.actorType == "bots" && message.actorId == "changelog") { } else if (message.actorType == "bots" && message.actorId == "changelog") {
if (context != null) { binding.messageUserAvatar.loadChangelogBotAvatar()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val layers = arrayOfNulls<Drawable>(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)
}
}
} else if (message.actorType == "bots") { } else if (message.actorType == "bots") {
val drawable = TextDrawable.builder() binding.messageUserAvatar.loadBotsAvatar()
.beginConfig()
.bold()
.endConfig()
.buildRound(
">",
ResourcesCompat.getColor(context!!.resources, R.color.black, null)
)
binding.messageUserAvatar.visibility = View.VISIBLE
binding.messageUserAvatar.setImageDrawable(drawable)
} }
} }

View File

@ -28,9 +28,6 @@ package com.nextcloud.talk.adapters.messages
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.os.Build
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import android.view.View 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
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding
import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders import com.stfalcon.chatkit.messages.MessageHolders
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
@ -245,15 +242,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message
if (message.actorType == "guests") { if (message.actorType == "guests") {
// do nothing, avatar is set // do nothing, avatar is set
} else if (message.actorType == "bots" && message.actorId == "changelog") { } else if (message.actorType == "bots" && message.actorId == "changelog") {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { binding.messageUserAvatar.loadChangelogBotAvatar()
val layers = arrayOfNulls<Drawable>(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)
}
} else if (message.actorType == "bots") { } else if (message.actorType == "bots") {
val drawable = TextDrawable.builder() val drawable = TextDrawable.builder()
.beginConfig() .beginConfig()

View File

@ -25,14 +25,12 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import com.facebook.drawee.backends.pipeline.Fresco import coil.load
import com.facebook.drawee.interfaces.DraweeController
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.databinding.ReferenceInsideMessageBinding import com.nextcloud.talk.databinding.ReferenceInsideMessageBinding
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.opengraph.OpenGraphOverall import com.nextcloud.talk.models.json.opengraph.OpenGraphOverall
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import io.reactivex.Observer import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
@ -92,12 +90,7 @@ class LinkPreview {
val referenceThumbUrl = reference.openGraphObject?.thumb val referenceThumbUrl = reference.openGraphObject?.thumb
if (!referenceThumbUrl.isNullOrEmpty()) { if (!referenceThumbUrl.isNullOrEmpty()) {
binding.referenceThumbImage.visibility = View.VISIBLE binding.referenceThumbImage.visibility = View.VISIBLE
val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() binding.referenceThumbImage.load(referenceThumbUrl)
.setAutoPlayAnimations(true)
.setImageRequest(DisplayUtils.getImageRequestForUrl(referenceThumbUrl))
.build()
binding.referenceThumbImage.controller =
draweeController
} else { } else {
binding.referenceThumbImage.visibility = View.GONE binding.referenceThumbImage.visibility = View.GONE
} }

View File

@ -23,9 +23,9 @@
package com.nextcloud.talk.adapters.messages; package com.nextcloud.talk.adapters.messages;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import com.facebook.drawee.view.SimpleDraweeView;
import com.google.android.material.card.MaterialCardView; import com.google.android.material.card.MaterialCardView;
import com.nextcloud.talk.R; import com.nextcloud.talk.R;
import com.nextcloud.talk.databinding.ItemCustomOutcomingPreviewMessageBinding; import com.nextcloud.talk.databinding.ItemCustomOutcomingPreviewMessageBinding;
@ -75,7 +75,7 @@ public class OutcomingPreviewMessageViewHolder extends PreviewMessageViewHolder
} }
@Override @Override
public SimpleDraweeView getPreviewContactPhoto() { public ImageView getPreviewContactPhoto() {
return binding.contactPhoto; return binding.contactPhoto;
} }

View File

@ -5,7 +5,7 @@
* @author Marcel Hibbe * @author Marcel Hibbe
* @author Andy Scherzinger * @author Andy Scherzinger
* @author Tim Krüger * @author Tim Krüger
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me> * Copyright (C) 2021-2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de> * Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
@ -30,7 +30,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.net.Uri import android.net.Uri
import android.os.Handler import android.os.Handler
import android.util.Base64 import android.util.Base64
@ -38,13 +37,13 @@ import android.util.Log
import android.view.Gravity import android.view.Gravity
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.ImageView
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.ProgressBar import android.widget.ProgressBar
import androidx.appcompat.view.ContextThemeWrapper import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.emoji.widget.EmojiTextView import androidx.emoji.widget.EmojiTextView
import autodagger.AutoInjector import autodagger.AutoInjector
import com.facebook.drawee.view.SimpleDraweeView
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication 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.components.filebrowser.webdav.ReadFilesystemOperation
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding
import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
@ -92,6 +92,8 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
lateinit var commonMessageInterface: CommonMessageInterface lateinit var commonMessageInterface: CommonMessageInterface
var previewMessageInterface: PreviewMessageInterface? = null var previewMessageInterface: PreviewMessageInterface? = null
private var placeholder: Drawable? = null
init { init {
sharedApplication!!.componentApplication.inject(this) 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 (ACTOR_TYPE_BOTS == message.actorType && ACTOR_ID_CHANGELOG == message.actorId) {
if (context != null) { userAvatar.loadChangelogBotAvatar()
val layers = arrayOfNulls<Drawable>(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))
}
} }
} }
} }
@ -150,11 +146,10 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
} }
if (message.selectedIndividualHashMap!!.containsKey(KEY_CONTACT_PHOTO)) { if (message.selectedIndividualHashMap!!.containsKey(KEY_CONTACT_PHOTO)) {
image = previewContactPhoto image = previewContactPhoto
val drawable = getDrawableFromContactDetails( placeholder = getDrawableFromContactDetails(
context, context,
message.selectedIndividualHashMap!![KEY_CONTACT_PHOTO] message.selectedIndividualHashMap!![KEY_CONTACT_PHOTO]
) )
image.hierarchy.setPlaceholderImage(drawable)
} else if (message.selectedIndividualHashMap!!.containsKey(KEY_MIMETYPE)) { } else if (message.selectedIndividualHashMap!!.containsKey(KEY_MIMETYPE)) {
val mimetype = message.selectedIndividualHashMap!![KEY_MIMETYPE] val mimetype = message.selectedIndividualHashMap!![KEY_MIMETYPE]
val drawableResourceId = getDrawableResourceIdForMimeType(mimetype) val drawableResourceId = getDrawableResourceIdForMimeType(mimetype)
@ -170,7 +165,7 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
PorterDuff.Mode.SRC_ATOP PorterDuff.Mode.SRC_ATOP
) )
} }
image.hierarchy.setPlaceholderImage(drawable) placeholder = drawable
} else { } else {
fetchFileInformation( fetchFileInformation(
"/" + message.selectedIndividualHashMap!![KEY_PATH], "/" + message.selectedIndividualHashMap!![KEY_PATH],
@ -208,13 +203,13 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
DisplayUtils.setClickableString("Tenor", "https://tenor.com", messageText) DisplayUtils.setClickableString("Tenor", "https://tenor.com", messageText)
} else { } else {
if (message.messageType == ChatMessage.MessageType.SINGLE_LINK_IMAGE_MESSAGE.name) { 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)) val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(message.imageUrl))
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context!!.startActivity(browserIntent) context!!.startActivity(browserIntent)
} }
} else { } else {
(clickView as SimpleDraweeView?)?.setOnClickListener(null) clickView!!.setOnClickListener(null)
} }
messageText.text = "" messageText.text = ""
} }
@ -238,6 +233,10 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
commonMessageInterface.onClickReaction(chatMessage, emoji) commonMessageInterface.onClickReaction(chatMessage, emoji)
} }
override fun getPayloadForImageLoader(message: ChatMessage?): Any? {
return placeholder
}
private fun getDrawableFromContactDetails(context: Context?, base64: String?): Drawable? { private fun getDrawableFromContactDetails(context: Context?, base64: String?): Drawable? {
var drawable: Drawable? = null var drawable: Drawable? = null
if (base64 != "") { if (base64 != "") {
@ -300,8 +299,7 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
if (browserFileList.isNotEmpty()) { if (browserFileList.isNotEmpty()) {
Handler(context!!.mainLooper).post { Handler(context!!.mainLooper).post {
val resourceId = getDrawableResourceIdForMimeType(browserFileList[0].mimeType) val resourceId = getDrawableResourceIdForMimeType(browserFileList[0].mimeType)
val drawable = ContextCompat.getDrawable(context!!, resourceId) placeholder = ContextCompat.getDrawable(context!!, resourceId)
image.hierarchy.setPlaceholderImage(drawable)
} }
} }
} }
@ -324,7 +322,7 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
abstract val messageText: EmojiTextView abstract val messageText: EmojiTextView
abstract val previewContainer: View abstract val previewContainer: View
abstract val previewContactContainer: MaterialCardView abstract val previewContactContainer: MaterialCardView
abstract val previewContactPhoto: SimpleDraweeView abstract val previewContactPhoto: ImageView
abstract val previewContactName: EmojiTextView abstract val previewContactName: EmojiTextView
abstract val previewContactProgressBar: ProgressBar? abstract val previewContactProgressBar: ProgressBar?

View File

@ -4,6 +4,8 @@
* @author Marcel Hibbe * @author Marcel Hibbe
* @author Andy Scherzinger * @author Andy Scherzinger
* @author Mario Danic * @author Mario Danic
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de> * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
@ -46,9 +48,7 @@ import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder import coil.decode.ImageDecoderDecoder
import coil.decode.SvgDecoder import coil.decode.SvgDecoder
import coil.memory.MemoryCache import coil.memory.MemoryCache
import com.facebook.cache.disk.DiskCacheConfig import coil.util.DebugLogger
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.components.filebrowser.webdav.DavUtils import com.nextcloud.talk.components.filebrowser.webdav.DavUtils
import com.nextcloud.talk.dagger.modules.BusModule import com.nextcloud.talk.dagger.modules.BusModule
@ -64,9 +64,7 @@ import com.nextcloud.talk.jobs.SignalingSettingsWorker
import com.nextcloud.talk.ui.theme.ThemeModule import com.nextcloud.talk.ui.theme.ThemeModule
import com.nextcloud.talk.utils.ClosedInterfaceImpl import com.nextcloud.talk.utils.ClosedInterfaceImpl
import com.nextcloud.talk.utils.DeviceUtils import com.nextcloud.talk.utils.DeviceUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.NotificationUtils 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.arbitrarystorage.ArbitraryStorageModule
import com.nextcloud.talk.utils.database.user.UserModule import com.nextcloud.talk.utils.database.user.UserModule
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
@ -164,7 +162,6 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
securityKeyManager.init(this, securityKeyConfig) securityKeyManager.init(this, securityKeyConfig)
initializeWebRtc() initializeWebRtc()
DisplayUtils.useCompatVectorIfNeeded()
buildComponent() buildComponent()
DavUtils.registerCustomFactories() DavUtils.registerCustomFactories()
@ -174,18 +171,6 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
setAppTheme(appPreferences.theme) setAppTheme(appPreferences.theme)
super.onCreate() 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) Security.insertProviderAt(Conscrypt.newProvider(), 1)
ClosedInterfaceImpl().providerInstallerInstallIfNeededAsync() ClosedInterfaceImpl().providerInstallerInstallIfNeededAsync()
@ -240,7 +225,7 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
} }
private fun buildDefaultImageLoader(): ImageLoader { private fun buildDefaultImageLoader(): ImageLoader {
return ImageLoader.Builder(applicationContext) val imageLoaderBuilder = ImageLoader.Builder(applicationContext)
.memoryCache { .memoryCache {
// Use 50% of the application's available memory. // Use 50% of the application's available memory.
MemoryCache.Builder(applicationContext).maxSizePercent(FIFTY_PERCENT).build() MemoryCache.Builder(applicationContext).maxSizePercent(FIFTY_PERCENT).build()
@ -254,8 +239,12 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
} }
add(SvgDecoder.Factory()) add(SvgDecoder.Factory())
} }
.okHttpClient(okHttpClient)
.build() if (BuildConfig.DEBUG) {
imageLoaderBuilder.logger(DebugLogger())
}
return imageLoaderBuilder.build()
} }
companion object { companion object {

View File

@ -27,7 +27,7 @@ import android.text.Editable;
import android.text.Spanned; import android.text.Spanned;
import android.widget.EditText; 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.R;
import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.models.json.mention.Mention; import com.nextcloud.talk.models.json.mention.Mention;

View File

@ -5,7 +5,7 @@
* @author Marcel Hibbe * @author Marcel Hibbe
* @author Andy Scherzinger * @author Andy Scherzinger
* @author Tim Krüger * @author Tim Krüger
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me> * Copyright (C) 2021-2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de> * Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
@ -37,8 +37,9 @@ import android.content.pm.PackageManager
import android.content.res.AssetFileDescriptor import android.content.res.AssetFileDescriptor
import android.content.res.Resources import android.content.res.Resources
import android.database.Cursor import android.database.Cursor
import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.media.MediaPlayer import android.media.MediaPlayer
import android.media.MediaRecorder import android.media.MediaRecorder
import android.net.Uri import android.net.Uri
@ -78,7 +79,7 @@ import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.content.PermissionChecker import androidx.core.content.PermissionChecker
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.graphics.drawable.toBitmap
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import androidx.emoji.text.EmojiCompat import androidx.emoji.text.EmojiCompat
import androidx.emoji.widget.EmojiTextView import androidx.emoji.widget.EmojiTextView
@ -90,15 +91,13 @@ import androidx.work.OneTimeWorkRequest
import androidx.work.WorkInfo import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import autodagger.AutoInjector import autodagger.AutoInjector
import coil.imageLoader
import coil.load import coil.load
import coil.request.ImageRequest
import coil.target.Target
import coil.transform.CircleCropTransformation
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler 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.flexbox.FlexboxLayout
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.talk.BuildConfig 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.databinding.ControllerChatBinding
import com.nextcloud.talk.events.UserMentionClickEvent import com.nextcloud.talk.events.UserMentionClickEvent
import com.nextcloud.talk.events.WebSocketCommunicationEvent import com.nextcloud.talk.events.WebSocketCommunicationEvent
import com.nextcloud.talk.extensions.loadAvatarOrImagePreview
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
import com.nextcloud.talk.jobs.ShareOperationWorker import com.nextcloud.talk.jobs.ShareOperationWorker
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker 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.ConductorRemapping.remapChatController
import com.nextcloud.talk.utils.ContactUtils import com.nextcloud.talk.utils.ContactUtils
import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.FileUtils import com.nextcloud.talk.utils.FileUtils
import com.nextcloud.talk.utils.ImageEmojiEditText import com.nextcloud.talk.utils.ImageEmojiEditText
import com.nextcloud.talk.utils.MagicCharPolicy import com.nextcloud.talk.utils.MagicCharPolicy
@ -462,42 +461,48 @@ class ChatController(args: Bundle) :
private fun loadAvatarForStatusBar() { private fun loadAvatarForStatusBar() {
if (isOneToOneConversation() && activity != null) { if (isOneToOneConversation() && activity != null) {
val imageRequest = DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar( val url = ApiUtils.getUrlForAvatar(
conversationUser?.baseUrl, conversationUser!!.baseUrl,
currentConversation?.name, currentConversation!!.name,
true true
),
conversationUser!!
) )
val target = object : Target {
val imagePipeline = Fresco.getImagePipeline() private fun setIcon(drawable: Drawable?) {
val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null)
dataSource.subscribe( actionBar?.let {
object : BaseBitmapDataSubscriber() { val avatarSize = (it.height / TOOLBAR_AVATAR_RATIO).roundToInt()
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)
val roundedBitmapDrawable = if (drawable != null && avatarSize > 0) {
RoundedBitmapDrawableFactory.create(resources!!, bitmapResized) val bitmap = drawable.toBitmap(avatarSize, avatarSize)
roundedBitmapDrawable.isCircular = true it.setIcon(BitmapDrawable(resources, bitmap))
roundedBitmapDrawable.setAntiAlias(true)
actionBar?.setIcon(roundedBitmapDrawable)
} else { } else {
Log.d(TAG, "loadAvatarForStatusBar avatarSize <= 0") Log.d(TAG, "loadAvatarForStatusBar avatarSize <= 0")
} }
} }
} }
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage>>) { override fun onStart(placeholder: Drawable?) {
// unused atm this.setIcon(placeholder)
} }
},
UiThreadImmediateExecutorService.getInstance() 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( adapter = TalkMessagesListAdapter(
senderId, senderId,
messageHolders, messageHolders,
ImageLoader { imageView, url, payload -> ImageLoader { imageView, url, _ ->
val draweeController = Fresco.newDraweeControllerBuilder() imageView.loadAvatarOrImagePreview(url!!, conversationUser, placeholder = payload as? Drawable)
.setImageRequest(DisplayUtils.getImageRequestForUrl(url, conversationUser))
.setControllerListener(DisplayUtils.getImageControllerListener(imageView))
.setOldController(imageView.controller)
.setAutoPlayAnimations(true)
.build()
imageView.controller = draweeController
}, },
this this
) )
@ -1150,14 +1149,10 @@ class ChatController(args: Bundle) :
} }
private fun isRecordAudioPermissionGranted(): Boolean { private fun isRecordAudioPermissionGranted(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return PermissionChecker.checkSelfPermission( return PermissionChecker.checkSelfPermission(
context, context,
Manifest.permission.RECORD_AUDIO Manifest.permission.RECORD_AUDIO
) == PermissionChecker.PERMISSION_GRANTED ) == PermissionChecker.PERMISSION_GRANTED
} else {
true
}
} }
private fun startAudioRecording(file: String) { private fun startAudioRecording(file: String) {

View File

@ -28,9 +28,6 @@ package com.nextcloud.talk.controllers
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent 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.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.text.TextUtils import android.text.TextUtils
@ -42,7 +39,6 @@ import android.view.View.VISIBLE
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SwitchCompat import androidx.appcompat.widget.SwitchCompat
import androidx.core.content.ContextCompat
import androidx.work.Data import androidx.work.Data
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
@ -53,7 +49,6 @@ import com.afollestad.materialdialogs.bottomsheets.BottomSheet
import com.afollestad.materialdialogs.datetime.dateTimePicker import com.afollestad.materialdialogs.datetime.dateTimePicker
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.facebook.drawee.backends.pipeline.Fresco
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.adapters.items.ParticipantItem 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.data.user.model.User
import com.nextcloud.talk.databinding.ControllerConversationInfoBinding import com.nextcloud.talk.databinding.ControllerConversationInfoBinding
import com.nextcloud.talk.events.EventStatus 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.DeleteConversationWorker
import com.nextcloud.talk.jobs.LeaveConversationWorker import com.nextcloud.talk.jobs.LeaveConversationWorker
import com.nextcloud.talk.models.json.conversations.Conversation 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.ApiUtils
import com.nextcloud.talk.utils.DateConstants import com.nextcloud.talk.utils.DateConstants
import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule
@ -778,54 +776,16 @@ class ConversationInfoController(args: Bundle) :
private fun loadConversationAvatar() { private fun loadConversationAvatar() {
when (conversation!!.type) { when (conversation!!.type) {
Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(conversation!!.name)) { Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(conversation!!.name)) {
val draweeController = Fresco.newDraweeControllerBuilder() conversation!!.name?.let { binding.avatarImage.loadAvatar(conversationUser!!, it) }
.setOldController(binding.avatarImage.controller)
.setAutoPlayAnimations(true)
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar(
conversationUser!!.baseUrl,
conversation!!.name,
true
),
conversationUser
)
)
.build()
binding.avatarImage.controller = draweeController
} }
Conversation.ConversationType.ROOM_GROUP_CALL -> { Conversation.ConversationType.ROOM_GROUP_CALL -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { binding.avatarImage.loadGroupCallAvatar(viewThemeUtils)
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
)
}
} }
Conversation.ConversationType.ROOM_PUBLIC_CALL -> { Conversation.ConversationType.ROOM_PUBLIC_CALL -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { binding.avatarImage.loadPublicCallAvatar(viewThemeUtils)
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
)
}
} }
Conversation.ConversationType.ROOM_SYSTEM -> { Conversation.ConversationType.ROOM_SYSTEM -> {
val layers = arrayOfNulls<Drawable>(2) binding.avatarImage.loadSystemAvatar()
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))
} }
else -> { else -> {

View File

@ -31,7 +31,7 @@ import android.app.SearchManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -49,8 +49,6 @@ import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import androidx.core.view.MenuItemCompat import androidx.core.view.MenuItemCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -58,15 +56,13 @@ import androidx.work.Data
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
import autodagger.AutoInjector 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.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler 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.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.talk.R 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.ApiUtils
import com.nextcloud.talk.utils.ClosedInterfaceImpl import com.nextcloud.talk.utils.ClosedInterfaceImpl
import com.nextcloud.talk.utils.ConductorRemapping.remapChatController import com.nextcloud.talk.utils.ConductorRemapping.remapChatController
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.FileUtils import com.nextcloud.talk.utils.FileUtils
import com.nextcloud.talk.utils.Mimetype import com.nextcloud.talk.utils.Mimetype
import com.nextcloud.talk.utils.ParticipantPermissions import com.nextcloud.talk.utils.ParticipantPermissions
@ -211,78 +206,56 @@ class ConversationsListController(bundle: Bundle) :
prepareViews() prepareViews()
} }
private fun loadUserAvatar(button: MaterialButton) { private fun loadUserAvatar(
target: Target
) {
if (activity != null) { if (activity != null) {
val imageRequest = DisplayUtils.getImageRequestForUrl( val url = ApiUtils.getUrlForAvatar(
ApiUtils.getUrlForAvatar(
currentUser!!.baseUrl, currentUser!!.baseUrl,
currentUser!!.userId, currentUser!!.userId,
true true
),
currentUser
) )
val imagePipeline = Fresco.getImagePipeline()
val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null) val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
dataSource.subscribe(
object : BaseBitmapDataSubscriber() { context.imageLoader.enqueue(
override fun onNewResultImpl(bitmap: Bitmap?) { ImageRequest.Builder(context)
if (bitmap != null && resources != null) { .data(url)
val roundedBitmapDrawable = RoundedBitmapDrawableFactory.create( .addHeader("Authorization", credentials)
resources!!, .placeholder(R.drawable.ic_user)
bitmap .transformations(CircleCropTransformation())
.crossfade(true)
.target(target)
.build()
) )
roundedBitmapDrawable.isCircular = true
roundedBitmapDrawable.setAntiAlias(true)
button.icon = roundedBitmapDrawable
} }
} }
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage?>>) { private fun loadUserAvatar(button: MaterialButton) {
if (resources != null) {
button.icon = ResourcesCompat.getDrawable(resources!!, R.drawable.ic_user, null) val target = object : Target {
override fun onStart(placeholder: Drawable?) {
button.icon = placeholder
}
override fun onSuccess(result: Drawable) {
button.icon = result
} }
} }
},
UiThreadImmediateExecutorService.getInstance() loadUserAvatar(target)
)
}
} }
private fun loadUserAvatar(menuItem: MenuItem) { private fun loadUserAvatar(menuItem: MenuItem) {
if (activity != null) { val target = object : Target {
val imageRequest = DisplayUtils.getImageRequestForUrl( override fun onStart(placeholder: Drawable?) {
ApiUtils.getUrlForAvatar( menuItem.icon = placeholder
currentUser!!.baseUrl, }
currentUser!!.userId, override fun onSuccess(result: Drawable) {
true menuItem.icon = result
),
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
} }
} }
loadUserAvatar(target)
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage?>>) {
if (resources != null) {
menuItem.icon = ResourcesCompat.getDrawable(resources!!, R.drawable.ic_user, null)
}
}
},
UiThreadImmediateExecutorService.getInstance()
)
}
} }
override fun onAttach(view: View) { override fun onAttach(view: View) {
@ -585,15 +558,15 @@ class ConversationsListController(bundle: Bundle) :
if (activity != null) { if (activity != null) {
val conversationItem = ConversationItem( val conversationItem = ConversationItem(
conversation, conversation,
currentUser, currentUser!!,
activity, activity!!,
viewThemeUtils viewThemeUtils
) )
conversationItems.add(conversationItem) conversationItems.add(conversationItem)
val conversationItemWithHeader = ConversationItem( val conversationItemWithHeader = ConversationItem(
conversation, conversation,
currentUser, currentUser!!,
activity, activity!!,
callHeaderItems[headerTitle], callHeaderItems[headerTitle],
viewThemeUtils viewThemeUtils
) )
@ -651,8 +624,8 @@ class ConversationsListController(bundle: Bundle) :
} }
val conversationItem = ConversationItem( val conversationItem = ConversationItem(
conversation, conversation,
currentUser, currentUser!!,
activity, activity!!,
callHeaderItems[headerTitle], callHeaderItems[headerTitle],
viewThemeUtils viewThemeUtils
) )

View File

@ -442,23 +442,19 @@ class LocationPickerController(args: Bundle) :
private fun isLocationPermissionsGranted(): Boolean { private fun isLocationPermissionsGranted(): Boolean {
fun isCoarseLocationGranted(): Boolean { fun isCoarseLocationGranted(): Boolean {
return PermissionChecker.checkSelfPermission( return PermissionChecker.checkSelfPermission(
context!!, context,
Manifest.permission.ACCESS_COARSE_LOCATION Manifest.permission.ACCESS_COARSE_LOCATION
) == PermissionChecker.PERMISSION_GRANTED ) == PermissionChecker.PERMISSION_GRANTED
} }
fun isFineLocationGranted(): Boolean { fun isFineLocationGranted(): Boolean {
return PermissionChecker.checkSelfPermission( return PermissionChecker.checkSelfPermission(
context!!, context,
Manifest.permission.ACCESS_FINE_LOCATION Manifest.permission.ACCESS_FINE_LOCATION
) == PermissionChecker.PERMISSION_GRANTED ) == PermissionChecker.PERMISSION_GRANTED
} }
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return isCoarseLocationGranted() && isFineLocationGranted()
isCoarseLocationGranted() && isFineLocationGranted()
} else {
true
}
} }
private fun requestLocationPermissions() { private fun requestLocationPermissions() {

View File

@ -25,12 +25,10 @@ import android.app.Activity
import android.app.KeyguardManager import android.app.KeyguardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.PromptInfo import androidx.biometric.BiometricPrompt.PromptInfo
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
@ -62,15 +60,11 @@ class LockedController : BaseController(R.layout.controller_locked) {
override fun onViewBound(view: View) { override fun onViewBound(view: View) {
super.onViewBound(view) super.onViewBound(view)
sharedApplication!!.componentApplication.inject(this) sharedApplication!!.componentApplication.inject(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
binding.unlockContainer.setOnClickListener { binding.unlockContainer.setOnClickListener {
unlock() unlock()
} }
} }
}
@RequiresApi(api = Build.VERSION_CODES.M)
override fun onAttach(view: View) { override fun onAttach(view: View) {
super.onAttach(view) super.onAttach(view)
Log.d(TAG, "onAttach") Log.d(TAG, "onAttach")
@ -92,12 +86,10 @@ class LockedController : BaseController(R.layout.controller_locked) {
Log.d(TAG, "onDetach") Log.d(TAG, "onDetach")
} }
@RequiresApi(api = Build.VERSION_CODES.M)
fun unlock() { fun unlock() {
checkIfWeAreSecure() checkIfWeAreSecure()
} }
@RequiresApi(api = Build.VERSION_CODES.M)
private fun showBiometricDialog() { private fun showBiometricDialog() {
val context: Context? = activity val context: Context? = activity
if (context != null) { if (context != null) {
@ -140,11 +132,10 @@ class LockedController : BaseController(R.layout.controller_locked) {
} }
} }
@RequiresApi(api = Build.VERSION_CODES.M)
private fun checkIfWeAreSecure() { private fun checkIfWeAreSecure() {
val keyguardManager = activity?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager? val keyguardManager = activity?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager?
if (keyguardManager?.isKeyguardSecure == true && appPreferences!!.isScreenLocked) { if (keyguardManager?.isKeyguardSecure == true && appPreferences.isScreenLocked) {
if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences!!.screenLockTimeout)) { if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)) {
Log.d(TAG, "showBiometricDialog because 'we are NOT authenticated'...") Log.d(TAG, "showBiometricDialog because 'we are NOT authenticated'...")
showBiometricDialog() showBiometricDialog()
} else { } else {
@ -172,8 +163,7 @@ class LockedController : BaseController(R.layout.controller_locked) {
if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) { if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
if ( if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)
SecurityUtils.checkIfWeAreAuthenticated(appPreferences!!.screenLockTimeout)
) { ) {
Log.d(TAG, "All went well, dismiss locked controller") Log.d(TAG, "All went well, dismiss locked controller")
router.popCurrentController() router.popCurrentController()

View File

@ -157,10 +157,6 @@ class SettingsController : BaseController(R.layout.controller_settings) {
binding.settingsIncognitoKeyboard.visibility = View.GONE binding.settingsIncognitoKeyboard.visibility = View.GONE
} }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
binding.settingsScreenLock.visibility = View.GONE
binding.settingsScreenLockTimeout.visibility = View.GONE
} else {
binding.settingsScreenLock.setSummary( binding.settingsScreenLock.setSummary(
String.format( String.format(
Locale.getDefault(), Locale.getDefault(),
@ -168,7 +164,6 @@ class SettingsController : BaseController(R.layout.controller_settings) {
resources!!.getString(R.string.nc_app_product_name) resources!!.getString(R.string.nc_app_product_name)
) )
) )
}
setupPrivacyUrl() setupPrivacyUrl()
setupSourceCodeUrl() setupSourceCodeUrl()
@ -662,10 +657,8 @@ class SettingsController : BaseController(R.layout.controller_settings) {
appPreferences.isKeyboardIncognito appPreferences.isKeyboardIncognito
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
(binding.settingsIncognitoKeyboard.findViewById<View>(R.id.mp_checkable) as Checkable).isChecked = (binding.settingsIncognitoKeyboard.findViewById<View>(R.id.mp_checkable) as Checkable).isChecked =
appPreferences.isKeyboardIncognito appPreferences.isKeyboardIncognito
}
if (CapabilitiesUtilNew.isReadStatusAvailable(userManager.currentUser.blockingGet())) { if (CapabilitiesUtilNew.isReadStatusAvailable(userManager.currentUser.blockingGet())) {
(binding.settingsReadPrivacy.findViewById<View>(R.id.mp_checkable) as Checkable).isChecked = (binding.settingsReadPrivacy.findViewById<View>(R.id.mp_checkable) as Checkable).isChecked =
@ -679,7 +672,6 @@ class SettingsController : BaseController(R.layout.controller_settings) {
} }
private fun setupScreenLockSetting() { private fun setupScreenLockSetting() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
if (keyguardManager.isKeyguardSecure) { if (keyguardManager.isKeyguardSecure) {
binding.settingsScreenLock.isEnabled = true binding.settingsScreenLock.isEnabled = true
@ -703,7 +695,6 @@ class SettingsController : BaseController(R.layout.controller_settings) {
binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA
} }
} }
}
public override fun onDestroy() { public override fun onDestroy() {
appPreferences?.unregisterProxyTypeListener(proxyTypeChangeListener) appPreferences?.unregisterProxyTypeListener(proxyTypeChangeListener)
@ -805,11 +796,9 @@ class SettingsController : BaseController(R.layout.controller_settings) {
private inner class ScreenLockTimeoutListener : OnPreferenceValueChangedListener<String?> { private inner class ScreenLockTimeoutListener : OnPreferenceValueChangedListener<String?> {
override fun onChanged(newValue: String?) { override fun onChanged(newValue: String?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
SecurityUtils.createKey(appPreferences.screenLockTimeout) SecurityUtils.createKey(appPreferences.screenLockTimeout)
} }
} }
}
private inner class ScreenLockListener : OnPreferenceValueChangedListener<Boolean> { private inner class ScreenLockListener : OnPreferenceValueChangedListener<Boolean> {
override fun onChanged(newValue: Boolean) { override fun onChanged(newValue: Boolean) {

View File

@ -0,0 +1,283 @@
/*
* Nextcloud Talk application
*
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* 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 <https://www.gnu.org/licenses/>.
*/
@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<Drawable>(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<Drawable>(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
}
}

View File

@ -447,11 +447,9 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
EmojiCompat.get().process(pushMessage.text!!) EmojiCompat.get().process(pushMessage.text!!)
) )
} }
if (Build.VERSION.SDK_INT >= 23) {
// This method should exist since API 21, but some phones don't have it
// So as a safeguard, we don't use it until 23
notificationBuilder.color = context!!.resources.getColor(R.color.colorPrimary) notificationBuilder.color = context!!.resources.getColor(R.color.colorPrimary)
}
val notificationInfoBundle = Bundle() val notificationInfoBundle = Bundle()
notificationInfoBundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!) notificationInfoBundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!)
// could be an ID or a TOKEN // could be an ID or a TOKEN
@ -526,7 +524,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
notificationUser.id, notificationUser.id,
false false
) else ApiUtils.getUrlForGuestAvatar(baseUrl, notificationUser.name, false) ) else ApiUtils.getUrlForGuestAvatar(baseUrl, notificationUser.name, false)
person.setIcon(loadAvatarSync(avatarUrl)) person.setIcon(loadAvatarSync(avatarUrl, context!!))
} }
notificationBuilder.setStyle(getStyle(person.build(), style)) notificationBuilder.setStyle(getStyle(person.build(), style))
} }
@ -694,7 +692,6 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
.subscribe(object : Observer<ParticipantsOverall> { .subscribe(object : Observer<ParticipantsOverall> {
override fun onSubscribe(d: Disposable) = Unit override fun onSubscribe(d: Disposable) = Unit
@RequiresApi(Build.VERSION_CODES.M)
override fun onNext(participantsOverall: ParticipantsOverall) { override fun onNext(participantsOverall: ParticipantsOverall) {
val participantList: List<Participant> = participantsOverall.ocs!!.data!! val participantList: List<Participant> = participantsOverall.ocs!!.data!!
hasParticipantsInCall = participantList.isNotEmpty() hasParticipantsInCall = participantList.isNotEmpty()
@ -726,7 +723,6 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
Log.e(TAG, "Error in getPeersForCall", e) Log.e(TAG, "Error in getPeersForCall", e)
} }
@RequiresApi(Build.VERSION_CODES.M)
override fun onComplete() { override fun onComplete() {
if (isCallNotificationVisible) { if (isCallNotificationVisible) {
@ -821,7 +817,6 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
return PendingIntent.getActivity(context, requestCode, intent, intentFlag) return PendingIntent.getActivity(context, requestCode, intent, intentFlag)
} }
@RequiresApi(Build.VERSION_CODES.M)
private fun isCallNotificationVisible(decryptedPushMessage: DecryptedPushMessage): Boolean { private fun isCallNotificationVisible(decryptedPushMessage: DecryptedPushMessage): Boolean {
var isVisible = false var isVisible = false

View File

@ -295,7 +295,7 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
false false
} }
} }
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> { else -> {
if (PermissionChecker.checkSelfPermission( if (PermissionChecker.checkSelfPermission(
context, context,
Manifest.permission.WRITE_EXTERNAL_STORAGE Manifest.permission.WRITE_EXTERNAL_STORAGE
@ -308,10 +308,6 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
false false
} }
} }
else -> { // permission is automatically granted on sdk<23 upon installation
Log.d(TAG, "Permission is granted")
true
}
} }
} }
@ -325,7 +321,7 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
REQUEST_PERMISSION REQUEST_PERMISSION
) )
} }
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> { else -> {
controller.requestPermissions( controller.requestPermissions(
arrayOf( arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE Manifest.permission.WRITE_EXTERNAL_STORAGE
@ -333,8 +329,6 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
REQUEST_PERMISSION REQUEST_PERMISSION
) )
} }
else -> { // permission is automatically granted on sdk<23 upon installation
}
} }
} }

View File

@ -22,16 +22,15 @@ package com.nextcloud.talk.polls.adapters
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.text.TextUtils import android.text.TextUtils
import com.facebook.drawee.backends.pipeline.Fresco import android.widget.ImageView
import com.facebook.drawee.interfaces.DraweeController
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.PollResultVoterItemBinding 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.polls.model.PollDetails
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
class PollResultVoterViewHolder( class PollResultVoterViewHolder(
private val user: User, private val user: User,
@ -46,45 +45,19 @@ class PollResultVoterViewHolder(
binding.root.setOnClickListener { clickListener.onClick() } binding.root.setOnClickListener { clickListener.onClick() }
binding.pollVoterName.text = item.details.actorDisplayName binding.pollVoterName.text = item.details.actorDisplayName
binding.pollVoterAvatar.controller = getAvatarDraweeController(item.details) loadAvatar(item.details, binding.pollVoterAvatar)
viewThemeUtils.dialog.colorDialogSupportingText(binding.pollVoterName) viewThemeUtils.dialog.colorDialogSupportingText(binding.pollVoterName)
} }
private fun getAvatarDraweeController(pollDetail: PollDetails): DraweeController? { private fun loadAvatar(pollDetail: PollDetails, avatar: ImageView) {
var draweeController: DraweeController? = null
if (pollDetail.actorType == "guests") { if (pollDetail.actorType == "guests") {
var displayName = NextcloudTalkApplication.sharedApplication?.resources?.getString(R.string.nc_guest) var displayName = NextcloudTalkApplication.sharedApplication?.resources?.getString(R.string.nc_guest)
if (!TextUtils.isEmpty(pollDetail.actorDisplayName)) { if (!TextUtils.isEmpty(pollDetail.actorDisplayName)) {
displayName = pollDetail.actorDisplayName!! displayName = pollDetail.actorDisplayName!!
} }
draweeController = Fresco.newDraweeControllerBuilder() avatar.loadGuestAvatar(user, displayName!!, false)
.setAutoPlayAnimations(true)
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForGuestAvatar(
user.baseUrl,
displayName,
false
),
user
)
)
.build()
} else if (pollDetail.actorType == "users") { } else if (pollDetail.actorType == "users") {
draweeController = Fresco.newDraweeControllerBuilder() avatar.loadAvatar(user, pollDetail.actorId!!, false)
.setAutoPlayAnimations(true) }
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar(
user.baseUrl,
pollDetail.actorId,
false
),
user
)
)
.build()
}
return draweeController
} }
} }

View File

@ -2,6 +2,8 @@
* Nextcloud Talk application * Nextcloud Talk application
* *
* @author Marcel Hibbe * @author Marcel Hibbe
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
* *
* This program is free software: you can redistribute it and/or modify * 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.annotation.SuppressLint
import android.text.TextUtils import android.text.TextUtils
import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView 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.R
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.PollResultVotersOverviewItemBinding 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.polls.model.PollDetails
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
class PollResultVotersOverviewViewHolder( class PollResultVotersOverviewViewHolder(
private val user: User, private val user: User,
@ -61,24 +59,14 @@ class PollResultVotersOverviewViewHolder(
for (i in 0 until avatarsToDisplay) { for (i in 0 until avatarsToDisplay) {
val pollDetails = item.detailsList[i] val pollDetails = item.detailsList[i]
val avatar = SimpleDraweeView(binding.root.context) val avatar = ImageView(binding.root.context)
layoutParams.marginStart = i * AVATAR_OFFSET layoutParams.marginStart = i * AVATAR_OFFSET
avatar.layoutParams = layoutParams avatar.layoutParams = layoutParams
avatar.translationZ = i.toFloat() * -1 avatar.translationZ = i.toFloat() * -1
val roundingParams = RoundingParams.fromCornersRadius(AVATAR_RADIUS) loadAvatar(pollDetails, avatar)
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)
binding.votersAvatarsOverviewWrapper.addView(avatar) binding.votersAvatarsOverviewWrapper.addView(avatar)
@ -92,47 +80,20 @@ class PollResultVotersOverviewViewHolder(
} }
} }
private fun getAvatarDraweeController(pollDetail: PollDetails): DraweeController? { private fun loadAvatar(pollDetail: PollDetails, avatar: ImageView) {
var draweeController: DraweeController? = null
if (pollDetail.actorType == "guests") { if (pollDetail.actorType == "guests") {
var displayName = NextcloudTalkApplication.sharedApplication?.resources?.getString(R.string.nc_guest) var displayName = NextcloudTalkApplication.sharedApplication?.resources?.getString(R.string.nc_guest)
if (!TextUtils.isEmpty(pollDetail.actorDisplayName)) { if (!TextUtils.isEmpty(pollDetail.actorDisplayName)) {
displayName = pollDetail.actorDisplayName!! displayName = pollDetail.actorDisplayName!!
} }
draweeController = Fresco.newDraweeControllerBuilder() avatar.loadGuestAvatar(user, displayName!!, false)
.setAutoPlayAnimations(true)
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForGuestAvatar(
user.baseUrl,
displayName,
false
),
user
)
)
.build()
} else if (pollDetail.actorType == "users") { } else if (pollDetail.actorType == "users") {
draweeController = Fresco.newDraweeControllerBuilder() avatar.loadAvatar(user, pollDetail.actorId!!, false)
.setAutoPlayAnimations(true)
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar(
user.baseUrl,
pollDetail.actorId,
false
),
user
)
)
.build()
} }
return draweeController
} }
companion object { companion object {
const val AVATAR_SIZE = 60 const val AVATAR_SIZE = 60
const val AVATAR_RADIUS = 5f
const val MAX_AVATARS = 10 const val MAX_AVATARS = 10
const val AVATAR_OFFSET = AVATAR_SIZE - 20 const val AVATAR_OFFSET = AVATAR_SIZE - 20
const val DOTS_OFFSET = 70 const val DOTS_OFFSET = 70

View File

@ -47,6 +47,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SYSTEM_NOTIFICATION_ID import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SYSTEM_NOTIFICATION_ID
import io.reactivex.Observer import io.reactivex.Observer
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
@ -159,10 +160,11 @@ class DirectReplyReceiver : BroadcastReceiver() {
.extractMessagingStyleFromNotification(previousNotification) .extractMessagingStyleFromNotification(previousNotification)
// Add reply // Add reply
Single.fromCallable {
val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false) val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false)
val me = Person.Builder() val me = Person.Builder()
.setName(currentUser.displayName) .setName(currentUser.displayName)
.setIcon(NotificationUtils.loadAvatarSync(avatarUrl)) .setIcon(NotificationUtils.loadAvatarSync(avatarUrl, context))
.build() .build()
val message = NotificationCompat.MessagingStyle.Message(reply, System.currentTimeMillis(), me) val message = NotificationCompat.MessagingStyle.Message(reply, System.currentTimeMillis(), me)
previousStyle?.addMessage(message) previousStyle?.addMessage(message)
@ -170,9 +172,14 @@ class DirectReplyReceiver : BroadcastReceiver() {
// Set the updated style // Set the updated style
previousBuilder.setStyle(previousStyle) previousBuilder.setStyle(previousStyle)
// Update the active notification. // Check if notification still exists
if (findActiveNotification(systemNotificationId!!) != null) {
NotificationManagerCompat.from(context).notify(systemNotificationId!!, previousBuilder.build()) NotificationManagerCompat.from(context).notify(systemNotificationId!!, previousBuilder.build())
} }
}
.subscribeOn(Schedulers.io())
.subscribe()
}
companion object { companion object {
const val TAG = "DirectReplyReceiver" const val TAG = "DirectReplyReceiver"

View File

@ -22,20 +22,18 @@ package com.nextcloud.talk.remotefilebrowser.adapters
import android.text.format.Formatter import android.text.format.Formatter
import android.view.View import android.view.View
import android.widget.ImageView
import autodagger.AutoInjector 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.R
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.RvItemBrowserFileBinding import com.nextcloud.talk.databinding.RvItemBrowserFileBinding
import com.nextcloud.talk.extensions.loadImage
import com.nextcloud.talk.remotefilebrowser.SelectionInterface import com.nextcloud.talk.remotefilebrowser.SelectionInterface
import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DateUtils.getLocalDateTimeStringFromTimestamp import com.nextcloud.talk.utils.DateUtils.getLocalDateTimeStringFromTimestamp
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.Mimetype.FOLDER import com.nextcloud.talk.utils.Mimetype.FOLDER
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
@ -48,7 +46,7 @@ class RemoteFileBrowserItemsListViewHolder(
onItemClicked: (Int) -> Unit onItemClicked: (Int) -> Unit
) : RemoteFileBrowserItemsViewHolder(binding, mimeTypeSelectionFilter, currentUser, selectionInterface) { ) : RemoteFileBrowserItemsViewHolder(binding, mimeTypeSelectionFilter, currentUser, selectionInterface) {
override val fileIcon: SimpleDraweeView override val fileIcon: ImageView
get() = binding.fileIcon get() = binding.fileIcon
private var selectable: Boolean = true private var selectable: Boolean = true
@ -68,7 +66,6 @@ class RemoteFileBrowserItemsListViewHolder(
override fun onBind(item: RemoteFileBrowserItem) { override fun onBind(item: RemoteFileBrowserItem) {
super.onBind(item) super.onBind(item)
binding.fileIcon.controller = null
if (!item.isAllowedToReShare || item.isEncrypted) { if (!item.isAllowedToReShare || item.isEncrypted) {
binding.root.isEnabled = false binding.root.isEnabled = false
binding.root.alpha = DISABLED_ALPHA binding.root.alpha = DISABLED_ALPHA
@ -95,11 +92,7 @@ class RemoteFileBrowserItemsListViewHolder(
calculateClickability(item, selectable) calculateClickability(item, selectable)
setSelectability() setSelectability()
binding.fileIcon val placeholder = viewThemeUtils.talk.getPlaceholderImage(binding.root.context, item.mimeType)
.hierarchy
.setPlaceholderImage(
viewThemeUtils.talk.getPlaceholderImage(binding.root.context, item.mimeType)
)
if (item.hasPreview) { if (item.hasPreview) {
val path = ApiUtils.getUrlForFilePreviewWithRemotePath( val path = ApiUtils.getUrlForFilePreviewWithRemotePath(
@ -108,12 +101,10 @@ class RemoteFileBrowserItemsListViewHolder(
binding.fileIcon.context.resources.getDimensionPixelSize(R.dimen.small_item_height) binding.fileIcon.context.resources.getDimensionPixelSize(R.dimen.small_item_height)
) )
if (path.isNotEmpty()) { if (path.isNotEmpty()) {
val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() binding.fileIcon.loadImage(path, currentUser, placeholder)
.setAutoPlayAnimations(true)
.setImageRequest(DisplayUtils.getImageRequestForUrl(path))
.build()
binding.fileIcon.controller = draweeController
} }
} else {
binding.fileIcon.setImageDrawable(placeholder)
} }
binding.filenameTextView.text = item.displayName binding.filenameTextView.text = item.displayName

View File

@ -20,11 +20,9 @@
package com.nextcloud.talk.remotefilebrowser.adapters package com.nextcloud.talk.remotefilebrowser.adapters
import android.graphics.drawable.Drawable import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.facebook.drawee.view.SimpleDraweeView
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.remotefilebrowser.SelectionInterface import com.nextcloud.talk.remotefilebrowser.SelectionInterface
import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
@ -37,17 +35,9 @@ abstract class RemoteFileBrowserItemsViewHolder(
val selectionInterface: SelectionInterface, val selectionInterface: SelectionInterface,
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
abstract val fileIcon: SimpleDraweeView abstract val fileIcon: ImageView
open fun onBind(item: RemoteFileBrowserItem) { open fun onBind(item: RemoteFileBrowserItem) {
fileIcon.hierarchy.setPlaceholderImage(staticImage(item.mimeType, fileIcon)) fileIcon.setImageResource(DrawableUtils.getDrawableResourceIdForMimeType(item.mimeType))
}
private fun staticImage(
mimeType: String?,
image: SimpleDraweeView
): Drawable {
val drawableResourceId = DrawableUtils.getDrawableResourceIdForMimeType(mimeType)
return ContextCompat.getDrawable(image.context, drawableResourceId)!!
} }
} }

View File

@ -23,8 +23,8 @@
package com.nextcloud.talk.shareditems.adapters package com.nextcloud.talk.shareditems.adapters
import android.view.View import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar import android.widget.ProgressBar
import com.facebook.drawee.view.SimpleDraweeView
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.SharedItemGridBinding import com.nextcloud.talk.databinding.SharedItemGridBinding
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
@ -35,7 +35,7 @@ class SharedItemsGridViewHolder(
viewThemeUtils: ViewThemeUtils viewThemeUtils: ViewThemeUtils
) : SharedItemsViewHolder(binding, user, viewThemeUtils) { ) : SharedItemsViewHolder(binding, user, viewThemeUtils) {
override val image: SimpleDraweeView override val image: ImageView
get() = binding.image get() = binding.image
override val clickTarget: View override val clickTarget: View
get() = binding.image get() = binding.image

View File

@ -26,9 +26,10 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.text.format.Formatter import android.text.format.Formatter
import android.view.View import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar import android.widget.ProgressBar
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.facebook.drawee.view.SimpleDraweeView import coil.load
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.SharedItemListBinding import com.nextcloud.talk.databinding.SharedItemListBinding
@ -46,7 +47,7 @@ class SharedItemsListViewHolder(
viewThemeUtils: ViewThemeUtils viewThemeUtils: ViewThemeUtils
) : SharedItemsViewHolder(binding, user, viewThemeUtils) { ) : SharedItemsViewHolder(binding, user, viewThemeUtils) {
override val image: SimpleDraweeView override val image: ImageView
get() = binding.fileImage get() = binding.fileImage
override val clickTarget: View override val clickTarget: View
get() = binding.fileItem get() = binding.fileItem
@ -75,7 +76,7 @@ class SharedItemsListViewHolder(
binding.separator1.visibility = View.GONE binding.separator1.visibility = View.GONE
binding.fileDate.text = item.dateTime binding.fileDate.text = item.dateTime
binding.actor.text = item.actorName 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( image.setColorFilter(
ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon), ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon),
android.graphics.PorterDuff.Mode.SRC_IN android.graphics.PorterDuff.Mode.SRC_IN
@ -93,7 +94,7 @@ class SharedItemsListViewHolder(
binding.separator1.visibility = View.GONE binding.separator1.visibility = View.GONE
binding.fileDate.text = item.dateTime binding.fileDate.text = item.dateTime
binding.actor.text = item.actorName 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( image.setColorFilter(
ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon), ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon),
android.graphics.PorterDuff.Mode.SRC_IN android.graphics.PorterDuff.Mode.SRC_IN
@ -114,7 +115,7 @@ class SharedItemsListViewHolder(
binding.separator1.visibility = View.GONE binding.separator1.visibility = View.GONE
binding.fileDate.text = item.dateTime binding.fileDate.text = item.dateTime
binding.actor.text = item.actorName binding.actor.text = item.actorName
image.hierarchy.setPlaceholderImage(R.drawable.ic_mimetype_file) image.load(R.drawable.ic_mimetype_file)
image.setColorFilter( image.setColorFilter(
ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon), ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon),
android.graphics.PorterDuff.Mode.SRC_IN android.graphics.PorterDuff.Mode.SRC_IN
@ -129,7 +130,7 @@ class SharedItemsListViewHolder(
binding.separator1.visibility = View.GONE binding.separator1.visibility = View.GONE
binding.fileDate.text = item.dateTime binding.fileDate.text = item.dateTime
binding.actor.text = item.actorName binding.actor.text = item.actorName
image.hierarchy.setPlaceholderImage(R.drawable.ic_baseline_deck_24) image.load(R.drawable.ic_baseline_deck_24)
image.setColorFilter( image.setColorFilter(
ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon), ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon),
android.graphics.PorterDuff.Mode.SRC_IN android.graphics.PorterDuff.Mode.SRC_IN

View File

@ -23,29 +23,20 @@
package com.nextcloud.talk.shareditems.adapters package com.nextcloud.talk.shareditems.adapters
import android.content.Context import android.content.Context
import android.net.Uri
import android.util.Log
import android.view.View import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar import android.widget.ProgressBar
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding 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.data.user.model.User
import com.nextcloud.talk.extensions.loadImage
import com.nextcloud.talk.shareditems.model.SharedDeckCardItem import com.nextcloud.talk.shareditems.model.SharedDeckCardItem
import com.nextcloud.talk.shareditems.model.SharedFileItem import com.nextcloud.talk.shareditems.model.SharedFileItem
import com.nextcloud.talk.shareditems.model.SharedItem 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.SharedLocationItem
import com.nextcloud.talk.shareditems.model.SharedOtherItem import com.nextcloud.talk.shareditems.model.SharedOtherItem
import com.nextcloud.talk.shareditems.model.SharedPollItem 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 import com.nextcloud.talk.utils.FileViewerUtils
abstract class SharedItemsViewHolder( abstract class SharedItemsViewHolder(
@ -58,21 +49,21 @@ abstract class SharedItemsViewHolder(
private val TAG = SharedItemsViewHolder::class.simpleName private val TAG = SharedItemsViewHolder::class.simpleName
} }
abstract val image: SimpleDraweeView abstract val image: ImageView
abstract val clickTarget: View abstract val clickTarget: View
abstract val progressBar: ProgressBar abstract val progressBar: ProgressBar
private val authHeader = mapOf(
Pair(
"Authorization",
ApiUtils.getCredentials(user.username, user.token)
)
)
open fun onBind(item: SharedFileItem) { 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) { 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<ImageInfo?> = object : BaseControllerListener<ImageInfo?>() {
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: SharedPollItem, showPoll: (item: SharedItem, context: Context) -> Unit) {}
open fun onBind(item: SharedLocationItem) {} open fun onBind(item: SharedLocationItem) {}

View File

@ -33,8 +33,6 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.nextcloud.talk.activities.MainActivity; import com.nextcloud.talk.activities.MainActivity;
import com.nextcloud.talk.adapters.items.AdvancedUserItem; 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.application.NextcloudTalkApplication;
import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.databinding.DialogChooseAccountBinding; 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.participants.Participant;
import com.nextcloud.talk.models.json.status.Status; import com.nextcloud.talk.models.json.status.Status;
import com.nextcloud.talk.models.json.status.StatusOverall; import com.nextcloud.talk.models.json.status.StatusOverall;
@ -132,22 +131,10 @@ public class ChooseAccountDialogFragment extends DialogFragment {
if (user.getBaseUrl() != null && if (user.getBaseUrl() != null &&
(user.getBaseUrl().startsWith("http://") || user.getBaseUrl().startsWith("https://"))) { (user.getBaseUrl().startsWith("http://") || user.getBaseUrl().startsWith("https://"))) {
binding.currentAccount.userIcon.setVisibility(View.VISIBLE); binding.currentAccount.userIcon.setVisibility(View.VISIBLE);
ImageViewExtensionsKt.loadAvatar(binding.currentAccount.userIcon, user, user.getUserId(), true);
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);
} else { } else {
binding.currentAccount.userIcon.setVisibility(View.INVISIBLE); binding.currentAccount.userIcon.setVisibility(View.INVISIBLE);
} }
loadCurrentStatus(user); loadCurrentStatus(user);
} }
} }

View File

@ -33,8 +33,6 @@ import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import autodagger.AutoInjector 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.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.talk.activities.MainActivity import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.adapters.items.AdvancedUserItem 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.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.DialogChooseAccountShareToBinding import com.nextcloud.talk.databinding.DialogChooseAccountShareToBinding
import com.nextcloud.talk.extensions.loadAvatar
import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager 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.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import java.net.CookieManager import java.net.CookieManager
@ -97,21 +94,7 @@ class ChooseAccountShareToDialogFragment : DialogFragment() {
if (user.baseUrl != null && if (user.baseUrl != null &&
(user.baseUrl!!.startsWith("http://") || user.baseUrl!!.startsWith("https://")) (user.baseUrl!!.startsWith("http://") || user.baseUrl!!.startsWith("https://"))
) { ) {
binding!!.currentAccount.userIcon.visibility = View.VISIBLE binding!!.currentAccount.userIcon.loadAvatar(user, user.userId!!)
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
} else { } else {
binding!!.currentAccount.userIcon.visibility = View.INVISIBLE binding!!.currentAccount.userIcon.visibility = View.INVISIBLE
} }

View File

@ -3,6 +3,8 @@
* *
* @author Mario Danic * @author Mario Danic
* @author Andy Scherzinger * @author Andy Scherzinger
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
* *
@ -22,7 +24,6 @@
package com.nextcloud.talk.utils; package com.nextcloud.talk.utils;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -33,11 +34,7 @@ import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.VectorDrawable;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.text.Spannable; import android.text.Spannable;
@ -51,48 +48,26 @@ import android.text.style.AbsoluteSizeSpan;
import android.text.style.ClickableSpan; import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.view.Window; import android.view.Window;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView; 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.google.android.material.chip.ChipDrawable;
import com.nextcloud.talk.R; import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.events.UserMentionClickEvent; import com.nextcloud.talk.events.UserMentionClickEvent;
import com.nextcloud.talk.extensions.ImageViewExtensionsKt;
import com.nextcloud.talk.ui.theme.ViewThemeUtils; import com.nextcloud.talk.ui.theme.ViewThemeUtils;
import com.nextcloud.talk.utils.text.Spans; import com.nextcloud.talk.utils.text.Spans;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -103,12 +78,16 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.annotation.XmlRes; import androidx.annotation.XmlRes;
import androidx.appcompat.widget.AppCompatDrawableManager;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat; import androidx.core.content.res.ResourcesCompat;
import androidx.core.graphics.ColorUtils; import androidx.core.graphics.ColorUtils;
import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.graphics.drawable.DrawableCompat;
import androidx.emoji.text.EmojiCompat; 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_a_to_z_id;
import static com.nextcloud.talk.utils.FileSortOrder.sort_big_to_small_id; import static com.nextcloud.talk.utils.FileSortOrder.sort_big_to_small_id;
@ -119,8 +98,6 @@ import static com.nextcloud.talk.utils.FileSortOrder.sort_z_to_a_id;
public class DisplayUtils { public class DisplayUtils {
private static final String TAG = "DisplayUtils";
private static final int INDEX_LUMINATION = 2; private static final int INDEX_LUMINATION = 2;
private static final double MAX_LIGHTNESS = 0.92; private static final double MAX_LIGHTNESS = 0.92;
@ -154,33 +131,6 @@ public class DisplayUtils {
textView.setMovementMethod(LinkMovementMethod.getInstance()); 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) { public static Bitmap getBitmap(Drawable drawable) {
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), drawable.getIntrinsicHeight(),
@ -191,60 +141,6 @@ public class DisplayUtils {
return bitmap; return bitmap;
} }
public static ImageRequest getImageRequestForUrl(String url) {
return getImageRequestForUrl(url, (User) null);
}
public static ImageRequest getImageRequestForUrl(String url, @Nullable User user) {
Map<String, String> 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) { public static float convertDpToPixel(float dp, Context context) {
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
context.getResources().getDisplayMetrics()) + 0.5f); context.getResources().getDisplayMetrics()) + 0.5f);
@ -254,31 +150,6 @@ public class DisplayUtils {
return px / context.getResources().getDisplayMetrics().density; return px / context.getResources().getDisplayMetrics().density;
} }
// Solution inspired by https://stackoverflow.com/questions/34936590/why-isnt-my-vector-drawable-scaling-as-expected
public static void useCompatVectorIfNeeded() {
if (Build.VERSION.SDK_INT < 23) {
try {
@SuppressLint("RestrictedApi") AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
Class<?> inflateDelegateClass = Class.forName(
"android.support.v7.widget.AppCompatDrawableManager$InflateDelegate");
Class<?> vdcInflateDelegateClass = Class.forName(
"android.support.v7.widget.AppCompatDrawableManager$VdcInflateDelegate");
Constructor<?> constructor = vdcInflateDelegateClass.getDeclaredConstructor();
constructor.setAccessible(true);
Object vdcInflateDelegate = constructor.newInstance();
Class<?> args[] = {String.class, inflateDelegateClass};
Method addDelegate = AppCompatDrawableManager.class.getDeclaredMethod("addDelegate", args);
addDelegate.setAccessible(true);
addDelegate.invoke(drawableManager, "vector", vdcInflateDelegate);
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
InvocationTargetException | IllegalAccessException e) {
Log.e(TAG, "Failed to use reflection to enable proper vector scaling");
}
}
}
public static Drawable getTintedDrawable(Resources res, @DrawableRes int drawableResId, @ColorRes int colorResId) { public static Drawable getTintedDrawable(Resources res, @DrawableRes int drawableResId, @ColorRes int colorResId) {
Drawable drawable = ResourcesCompat.getDrawable(res, drawableResId, null); Drawable drawable = ResourcesCompat.getDrawable(res, drawableResId, null);
@ -305,10 +176,8 @@ public class DisplayUtils {
viewThemeUtils.material.colorChipDrawable(context, chip); viewThemeUtils.material.colorChipDrawable(context, chip);
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Configuration config = context.getResources().getConfiguration(); Configuration config = context.getResources().getConfiguration();
chip.setLayoutDirection(config.getLayoutDirection()); chip.setLayoutDirection(config.getLayoutDirection());
}
int drawable; int drawable;
@ -335,18 +204,25 @@ public class DisplayUtils {
conversationUser.getBaseUrl(), conversationUser.getBaseUrl(),
String.valueOf(label), true); String.valueOf(label), true);
} }
ImageRequest imageRequest = getImageRequestForUrl(url);
ImagePipeline imagePipeline = Fresco.getImagePipeline();
DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(
imageRequest,
context);
dataSource.subscribe( ImageRequest imageRequest = new ImageRequest.Builder(context)
new BaseBitmapDataSubscriber() { .data(url)
.crossfade(true)
.transformations(new CircleCropTransformation())
.target(new Target() {
@Override @Override
protected void onNewResultImpl(Bitmap bitmap) { public void onStart(@Nullable Drawable drawable) {
if (bitmap != null) {
chip.setChipIcon(getRoundedDrawable(new BitmapDrawable(bitmap))); }
@Override
public void onError(@Nullable Drawable drawable) {
}
@Override
public void onSuccess(@NonNull Drawable drawable) {
chip.setChipIcon(drawable);
// A hack to refresh the chip icon // A hack to refresh the chip icon
if (emojiEditText != null) { if (emojiEditText != null) {
@ -355,13 +231,10 @@ public class DisplayUtils {
TextView.BufferType.SPANNABLE)); TextView.BufferType.SPANNABLE));
} }
} }
} })
.build();
@Override Coil.imageLoader(context).enqueue(imageRequest);
protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
}
},
UiThreadImmediateExecutorService.getInstance());
} }
return chip; return chip;
@ -481,7 +354,7 @@ public class DisplayUtils {
Window window = activity.getWindow(); Window window = activity.getWindow();
boolean isLightTheme = lightTheme(color); boolean isLightTheme = lightTheme(color);
if (window != null) { if (window != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
View decor = window.getDecorView(); View decor = window.getDecorView();
if (isLightTheme) { if (isLightTheme) {
int systemUiFlagLightStatusBar; int systemUiFlagLightStatusBar;
@ -500,7 +373,6 @@ public class DisplayUtils {
window.setStatusBarColor(Color.BLACK); window.setStatusBarColor(Color.BLACK);
} }
} }
}
/** /**
* Tests if light color is set * Tests if light color is set
@ -575,7 +447,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; String avatarId;
if (!TextUtils.isEmpty(user.getUserId())) { if (!TextUtils.isEmpty(user.getUserId())) {
avatarId = user.getUserId(); avatarId = user.getUserId();
@ -583,50 +455,13 @@ public class DisplayUtils {
avatarId = user.getUsername(); avatarId = user.getUsername();
} }
String avatarString = ApiUtils.getUrlForAvatar(user.getBaseUrl(), avatarId, true);
// clear cache
if (deleteCache) { if (deleteCache) {
Uri avatarUri = Uri.parse(avatarString); ImageViewExtensionsKt.replaceAvatar(avatarImageView, user, avatarId, true);
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));
} else { } 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 public static @StringRes
int getSortOrderStringId(FileSortOrder sortOrder) { int getSortOrderStringId(FileSortOrder sortOrder) {
switch (sortOrder.getName()) { switch (sortOrder.getName()) {

View File

@ -22,7 +22,7 @@
package com.nextcloud.talk.utils package com.nextcloud.talk.utils
import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
import third_parties.daveKoeller.AlphanumComparator import third.parties.daveKoeller.AlphanumComparator
import java.util.Collections import java.util.Collections
class FileSortOrderByName internal constructor(name: String, ascending: Boolean) : FileSortOrder(name, ascending) { 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. * Comparator for RemoteFileBrowserItems, sorts by name.
*/ */
class RemoteFileBrowserItemNameComparator(private val multiplier: Int) : Comparator<RemoteFileBrowserItem> { class RemoteFileBrowserItemNameComparator(private val multiplier: Int) : Comparator<RemoteFileBrowserItem> {
private val alphanumComparator = AlphanumComparator<RemoteFileBrowserItem>() private val alphanumComparator =
AlphanumComparator<RemoteFileBrowserItem>()
override fun compare(left: RemoteFileBrowserItem, right: RemoteFileBrowserItem): Int { override fun compare(left: RemoteFileBrowserItem, right: RemoteFileBrowserItem): Int {
return if (!left.isFile && !right.isFile) { return if (!left.isFile && !right.isFile) {

View File

@ -28,6 +28,7 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.Toast import android.widget.Toast
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
@ -36,7 +37,6 @@ import androidx.work.Data
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import androidx.work.WorkInfo import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import com.facebook.drawee.view.SimpleDraweeView
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.activities.FullScreenImageActivity import com.nextcloud.talk.activities.FullScreenImageActivity
import com.nextcloud.talk.activities.FullScreenMediaActivity import com.nextcloud.talk.activities.FullScreenMediaActivity
@ -412,7 +412,7 @@ class FileViewerUtils(private val context: Context, private val user: User) {
data class ProgressUi( data class ProgressUi(
val progressBar: ProgressBar?, val progressBar: ProgressBar?,
val messageText: EmojiTextView?, val messageText: EmojiTextView?,
val previewImage: SimpleDraweeView val previewImage: ImageView
) )
data class FileInfo( data class FileInfo(

View File

@ -27,18 +27,19 @@ import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.graphics.drawable.BitmapDrawable
import android.media.AudioAttributes import android.media.AudioAttributes
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.service.notification.StatusBarNotification import android.service.notification.StatusBarNotification
import android.text.TextUtils import android.text.TextUtils
import android.util.Log
import androidx.core.graphics.drawable.IconCompat 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.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.BuildConfig
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
@ -50,6 +51,8 @@ import java.io.IOException
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
object NotificationUtils { object NotificationUtils {
const val TAG = "NotificationUtils"
enum class NotificationChannels { enum class NotificationChannels {
NOTIFICATION_CHANNEL_MESSAGES_V4, NOTIFICATION_CHANNEL_MESSAGES_V4,
NOTIFICATION_CHANNEL_CALLS_V4, NOTIFICATION_CHANNEL_CALLS_V4,
@ -215,7 +218,7 @@ object NotificationUtils {
notification: Notification notification: Notification
) -> Unit ) -> Unit
) { ) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || conversationUser.id == -1L || context == null) { if (conversationUser.id == -1L || context == null) {
return return
} }
@ -320,28 +323,30 @@ object NotificationUtils {
) )
} }
/* fun loadAvatarSync(avatarUrl: String, context: Context): IconCompat? {
* 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?
var avatarIcon: IconCompat? = null var avatarIcon: IconCompat? = null
val imageRequest = DisplayUtils.getImageRequestForUrl(avatarUrl)
val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, null) val request = ImageRequest.Builder(context)
val closeableImageRef = DataSources.waitForFinalResult(dataSource) as CloseableReference<CloseableBitmap>? .data(avatarUrl)
val bitmap = closeableImageRef?.get()?.underlyingBitmap .transformations(CircleCropTransformation())
if (bitmap != null) { .placeholder(R.drawable.account_circle_96dp)
// According to Fresco documentation a copy of the bitmap should be made before closing the references. .target(
// However, it seems to work without making a copy... ;-) onSuccess = { result ->
RoundAsCirclePostprocessor(true).process(bitmap) val bitmap = (result as BitmapDrawable).bitmap
avatarIcon = IconCompat.createWithBitmap(bitmap)
},
onError = { error ->
error?.let {
val bitmap = (error as BitmapDrawable).bitmap
avatarIcon = IconCompat.createWithBitmap(bitmap) avatarIcon = IconCompat.createWithBitmap(bitmap)
} }
CloseableReference.closeSafely(closeableImageRef) Log.w(TAG, "Can't load avatar for URL: $avatarUrl")
dataSource.close() }
)
.build()
context.imageLoader.executeBlocking(request)
return avatarIcon return avatarIcon
} }

View File

@ -1,41 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package 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);
}
}

View File

@ -21,7 +21,6 @@
package com.nextcloud.talk.utils; package com.nextcloud.talk.utils;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties; import android.security.keystore.KeyProperties;
@ -50,7 +49,6 @@ import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import androidx.annotation.RequiresApi;
import androidx.biometric.BiometricPrompt; import androidx.biometric.BiometricPrompt;
public class SecurityUtils { public class SecurityUtils {
@ -60,7 +58,6 @@ public class SecurityUtils {
private static BiometricPrompt.CryptoObject cryptoObject; private static BiometricPrompt.CryptoObject cryptoObject;
@RequiresApi(api = Build.VERSION_CODES.M)
public static boolean checkIfWeAreAuthenticated(String screenLockTimeout) { public static boolean checkIfWeAreAuthenticated(String screenLockTimeout) {
try { try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
@ -95,12 +92,10 @@ public class SecurityUtils {
} }
} }
@RequiresApi(api = Build.VERSION_CODES.M)
public static BiometricPrompt.CryptoObject getCryptoObject() { public static BiometricPrompt.CryptoObject getCryptoObject() {
return cryptoObject; return cryptoObject;
} }
@RequiresApi(api = Build.VERSION_CODES.M)
public static void createKey(String validity) { public static void createKey(String validity) {
try { try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");

View File

@ -23,7 +23,6 @@ package com.nextcloud.talk.utils.permissions
import android.Manifest import android.Manifest
import android.content.Context import android.content.Context
import android.os.Build
import androidx.core.content.PermissionChecker import androidx.core.content.PermissionChecker
import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.BuildConfig
@ -32,13 +31,9 @@ class PlatformPermissionUtilImpl(private val context: Context) : PlatformPermiss
"${BuildConfig.APPLICATION_ID}.${BuildConfig.PERMISSION_LOCAL_BROADCAST}" "${BuildConfig.APPLICATION_ID}.${BuildConfig.PERMISSION_LOCAL_BROADCAST}"
override fun isCameraPermissionGranted(): Boolean { override fun isCameraPermissionGranted(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return PermissionChecker.checkSelfPermission( return PermissionChecker.checkSelfPermission(
context, context,
Manifest.permission.CAMERA Manifest.permission.CAMERA
) == PermissionChecker.PERMISSION_GRANTED ) == PermissionChecker.PERMISSION_GRANTED
} else {
true
}
} }
} }

View File

@ -8,12 +8,9 @@
package com.nextcloud.talk.utils.ssl package com.nextcloud.talk.utils.ssl
import android.os.Build
import java.io.IOException
import java.net.InetAddress import java.net.InetAddress
import java.net.Socket import java.net.Socket
import java.security.GeneralSecurityException import java.security.GeneralSecurityException
import java.util.LinkedList
import javax.net.ssl.KeyManager import javax.net.ssl.KeyManager
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocket import javax.net.ssl.SSLSocket
@ -35,69 +32,12 @@ class SSLSocketFactoryCompat(
var cipherSuites: Array<String>? = null var cipherSuites: Array<String>? = null
init { init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Since Android 6.0 (API level 23), // Since Android 6.0 (API level 23),
// - TLSv1.1 and TLSv1.2 is enabled by default // - TLSv1.1 and TLSv1.2 is enabled by default
// - SSLv3 is disabled by default // - SSLv3 is disabled by default
// - all modern ciphers are activated by default // - all modern ciphers are activated by default
protocols = null protocols = null
cipherSuites = null cipherSuites = null
} else {
val socket = SSLSocketFactory.getDefault().createSocket() as SSLSocket?
try {
socket?.let {
/* set reasonable protocol versions */
// - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <5.0)
// - remove all SSL versions (especially SSLv3) because they're insecure now
val _protocols = LinkedList<String>()
for (protocol in socket.supportedProtocols.filterNot { it.contains("SSL", true) })
_protocols += protocol
protocols = _protocols.toTypedArray()
/* set up reasonable cipher suites */
val knownCiphers = arrayOf<String>(
// TLS 1.2
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
// maximum interoperability
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
"SSL_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
// additionally
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
)
val availableCiphers = socket.supportedCipherSuites
/* For maximum security, preferredCiphers should *replace* enabled ciphers (thus
* disabling ciphers which are enabled by default, but have become unsecure), but for
* the security level of DAVdroid and maximum compatibility, disabling of insecure
* ciphers should be a server-side task */
// for the final set of enabled ciphers, take the ciphers enabled by default, ...
val _cipherSuites = LinkedList<String>()
_cipherSuites.addAll(socket.enabledCipherSuites)
// ... add explicitly allowed ciphers ...
_cipherSuites.addAll(knownCiphers)
// ... and keep only those which are actually available
_cipherSuites.retainAll(availableCiphers)
cipherSuites = _cipherSuites.toTypedArray()
}
} catch (e: IOException) {
// Exception is to be ignored
} finally {
socket?.close() // doesn't implement Closeable on all supported Android versions
}
}
} }
} }

View File

@ -22,9 +22,10 @@ package com.nextcloud.talk.utils.text;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import com.facebook.widget.text.span.BetterImageSpan;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import third.parties.fresco.BetterImageSpan;
public class Spans { public class Spans {

View File

@ -41,7 +41,6 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.media.AudioDeviceInfo; import android.media.AudioDeviceInfo;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Build;
import android.util.Log; import android.util.Log;
import com.nextcloud.talk.events.PeerConnectionEvent; import com.nextcloud.talk.events.PeerConnectionEvent;
@ -388,9 +387,6 @@ public class WebRtcAudioManager {
*/ */
@Deprecated @Deprecated
private boolean hasWiredHeadset() { private boolean hasWiredHeadset() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return audioManager.isWiredHeadsetOn();
} else {
@SuppressLint("WrongConstant") final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL); @SuppressLint("WrongConstant") final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
for (AudioDeviceInfo device : devices) { for (AudioDeviceInfo device : devices) {
final int type = device.getType(); final int type = device.getType();
@ -404,7 +400,6 @@ public class WebRtcAudioManager {
} }
return false; return false;
} }
}
public void updateAudioDeviceState() { public void updateAudioDeviceState() {
ThreadUtils.checkIsOnMainThread(); ThreadUtils.checkIsOnMainThread();

View File

@ -22,7 +22,7 @@
* *
*/ */
package third_parties.daveKoeller; package third.parties.daveKoeller;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigInteger; import java.math.BigInteger;

View File

@ -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
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#000000" />
</shape>

View File

@ -21,7 +21,6 @@
--> -->
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="72dp" android:layout_height="72dp"
@ -44,7 +43,7 @@
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_centerVertical="true"> android:layout_centerVertical="true">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/user_icon" android:id="@+id/user_icon"
android:layout_width="@dimen/small_item_height" android:layout_width="@dimen/small_item_height"
android:layout_height="@dimen/small_item_height" android:layout_height="@dimen/small_item_height"
@ -54,10 +53,7 @@
android:layout_marginEnd="1dp" android:layout_marginEnd="1dp"
android:layout_marginBottom="1dp" android:layout_marginBottom="1dp"
android:contentDescription="@string/avatar" android:contentDescription="@string/avatar"
android:src="@drawable/account_circle_48dp" android:src="@drawable/account_circle_48dp" />
fresco:placeholderImage="@drawable/account_circle_48dp"
fresco:failureImage="@drawable/account_circle_48dp"
app:roundAsCircle="true"/>
</FrameLayout> </FrameLayout>

View File

@ -113,7 +113,8 @@
android:transitionName="userAvatar.transitionTag" android:transitionName="userAvatar.transitionTag"
app:cornerRadius="@dimen/button_corner_radius" app:cornerRadius="@dimen/button_corner_radius"
app:iconSize="@dimen/avatar_size_app_bar" app:iconSize="@dimen/avatar_size_app_bar"
tools:visibility="gone" /> app:iconTint="@null"
tools:icon="@drawable/ic_user" />
</FrameLayout> </FrameLayout>

View File

@ -2,9 +2,11 @@
~ Nextcloud Talk application ~ Nextcloud Talk application
~ ~
~ @author Mario Danic ~ @author Mario Danic
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
~ @author Marcel Hibbe ~ @author Marcel Hibbe
~ @author Tim Krüger
~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
~ Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de> ~ Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
~ ~
~ This program is free software: you can redistribute it and/or modify ~ 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 ~ it under the terms of the GNU General Public License as published by
@ -69,14 +71,14 @@
android:visibility="invisible" android:visibility="invisible"
tools:visibility="visible" /> tools:visibility="visible" />
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/switchSelfVideoButton" android:id="@+id/switchSelfVideoButton"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_gravity="center_horizontal|bottom" android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="20dp" android:layout_marginBottom="20dp"
app:placeholderImage="@drawable/ic_switch_video_white_24px" app:srcCompat="@drawable/ic_switch_video_white_24px"
app:roundAsCircle="true" /> android:contentDescription="@string/nc_call_button_content_description_switch_to_self_vide"/>
<ProgressBar <ProgressBar
android:id="@+id/selfVideoViewProgressBar" android:id="@+id/selfVideoViewProgressBar"
@ -144,6 +146,7 @@
android:id="@+id/callControls" android:id="@+id/callControls"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/call_controls_height" android:layout_height="@dimen/call_controls_height"
android:paddingHorizontal="@dimen/call_controls_padding_horizontal"
android:layout_alignBottom="@id/linearWrapperLayout" android:layout_alignBottom="@id/linearWrapperLayout"
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:background="@android:color/transparent" android:background="@android:color/transparent"
@ -151,94 +154,98 @@
android:orientation="horizontal" android:orientation="horizontal"
android:weightSum="5"> android:weightSum="5">
<com.facebook.drawee.view.SimpleDraweeView <ImageButton
android:id="@+id/pictureInPictureButton" android:id="@+id/pictureInPictureButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_marginStart="20dp" android:adjustViewBounds="true"
android:layout_marginEnd="10dp" android:layout_marginHorizontal="@dimen/call_controls_margin_horizontal"
android:elevation="10dp" android:layout_weight="1"
app:backgroundImage="@color/call_buttons_background" android:background="@drawable/shape_oval"
app:placeholderImage="@drawable/ic_baseline_picture_in_picture_alt_24" android:backgroundTint="@color/call_buttons_background"
app:roundAsCircle="true" app:srcCompat="@drawable/ic_baseline_picture_in_picture_alt_24"
android:layout_weight="1"/> android:contentDescription="@string/nc_call_button_content_description_pip" />
<com.facebook.drawee.view.SimpleDraweeView <ImageButton
android:id="@+id/audioOutputButton" android:id="@+id/audioOutputButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:adjustViewBounds="true"
android:layout_marginEnd="10dp" android:layout_marginHorizontal="@dimen/call_controls_margin_horizontal"
app:backgroundImage="@color/call_buttons_background" android:layout_weight="1"
app:placeholderImage="@drawable/ic_volume_mute_white_24dp" android:background="@drawable/shape_oval"
app:roundAsCircle="true" android:backgroundTint="@color/call_buttons_background"
android:layout_weight="1"/> app:srcCompat="@drawable/ic_volume_mute_white_24dp"
android:contentDescription="@string/nc_call_button_content_description_audio_output" />
<com.facebook.drawee.view.SimpleDraweeView <ImageButton
android:id="@+id/cameraButton" android:id="@+id/cameraButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:adjustViewBounds="true"
android:layout_marginEnd="10dp" android:layout_marginHorizontal="@dimen/call_controls_margin_horizontal"
android:layout_weight="1"
android:alpha="0.7" android:alpha="0.7"
app:backgroundImage="@color/call_buttons_background" android:background="@drawable/shape_oval"
app:placeholderImage="@drawable/ic_videocam_white_24px" android:backgroundTint="@color/call_buttons_background"
app:roundAsCircle="true" app:srcCompat="@drawable/ic_videocam_white_24px"
android:layout_weight="1"/> android:contentDescription="@string/nc_call_button_content_description_camera" />
<com.facebook.drawee.view.SimpleDraweeView <ImageButton
android:id="@+id/microphoneButton" android:id="@+id/microphoneButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:adjustViewBounds="true"
android:layout_marginEnd="10dp" android:layout_marginHorizontal="@dimen/call_controls_margin_horizontal"
android:layout_weight="1"
android:alpha="0.7" android:alpha="0.7"
app:backgroundImage="@color/call_buttons_background" android:background="@drawable/shape_oval"
app:placeholderImage="@drawable/ic_mic_off_white_24px" android:backgroundTint="@color/call_buttons_background"
app:roundAsCircle="true" app:srcCompat="@drawable/ic_mic_off_white_24px"
android:layout_weight="1"/> android:contentDescription="@string/nc_call_button_content_description_microphone" />
<com.facebook.drawee.view.SimpleDraweeView <ImageButton
android:id="@+id/hangupButton" android:id="@+id/hangupButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:adjustViewBounds="true"
android:layout_marginEnd="20dp" android:layout_marginHorizontal="@dimen/call_controls_margin_horizontal"
app:backgroundImage="@color/nc_darkRed" android:layout_weight="1"
app:placeholderImage="@drawable/ic_call_end_white_24px" android:background="@drawable/shape_oval"
app:roundAsCircle="true" android:backgroundTint="@color/nc_darkRed"
android:layout_weight="1"/> app:srcCompat="@drawable/ic_call_end_white_24px"
android:contentDescription="@string/nc_call_button_content_description_hangup" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/pipGroupCallOverlay" android:id="@+id/pipGroupCallOverlay"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/black" android:background="@color/black"
android:gravity="center" android:gravity="center"
android:orientation="vertical"
android:visibility="gone"> android:visibility="gone">
<TextView <TextView
android:id="@+id/pipCallConversationNameTextView" android:id="@+id/pipCallConversationNameTextView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="-30dp"
android:layout_marginBottom="15dp"
android:layout_marginStart="5dp" android:layout_marginStart="5dp"
android:layout_marginTop="-30dp"
android:layout_marginEnd="5dp" android:layout_marginEnd="5dp"
android:textAlignment="center" android:layout_marginBottom="15dp"
android:maxLines="3"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="3"
android:textAlignment="center"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" android:textSize="16sp"
tools:text="our group call" /> tools:text="our group call" />
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:layout_width="80dp" android:layout_width="80dp"
android:layout_height="80dp" android:layout_height="80dp"
app:backgroundImage="@drawable/ic_circular_group" app:srcCompat="@drawable/ic_circular_group"
app:roundAsCircle="true" /> tools:ignore="ContentDescription" />
</LinearLayout> </LinearLayout>

View File

@ -31,12 +31,12 @@
android:gravity="center" android:gravity="center"
android:orientation="vertical"> android:orientation="vertical">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/avatarImageView" android:id="@+id/avatarImageView"
android:layout_width="80dp" android:layout_width="80dp"
android:layout_height="80dp" android:layout_height="80dp"
android:layout_centerInParent="true" android:layout_centerInParent="true"
app:roundAsCircle="true" /> android:contentDescription="@string/avatar"/>
<org.webrtc.SurfaceViewRenderer <org.webrtc.SurfaceViewRenderer
android:id="@+id/surface_view" android:id="@+id/surface_view"

View File

@ -3,6 +3,8 @@
~ ~
~ @author Mario Danic ~ @author Mario Danic
~ @author Andy Scherzinger ~ @author Andy Scherzinger
~ @author Tim Krüger
~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com> ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
~ ~
@ -36,36 +38,37 @@
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:orientation="horizontal"> android:orientation="horizontal">
<com.facebook.drawee.view.SimpleDraweeView <ImageButton
android:id="@+id/callAnswerVoiceOnlyView" android:id="@+id/callAnswerVoiceOnlyView"
android:layout_width="60dp" android:layout_width="60dp"
android:layout_height="60dp" android:layout_height="60dp"
android:layout_margin="24dp" android:layout_margin="24dp"
android:visibility="gone" android:background="@drawable/shape_oval"
app:backgroundImage="@color/nc_darkGreen" android:backgroundTint="@color/nc_darkGreen"
app:placeholderImage="@drawable/ic_call_white_24dp" android:src="@drawable/ic_call_white_24dp"
app:roundAsCircle="true" android:contentDescription="@string/nc_call_button_content_description_answer_voice_only" />
tools:visibility="visible" />
<com.facebook.drawee.view.SimpleDraweeView <ImageButton
android:id="@+id/hangupButton" android:id="@+id/hangupButton"
android:layout_width="60dp" android:layout_width="60dp"
android:layout_height="60dp" android:layout_height="60dp"
android:layout_margin="24dp" android:layout_margin="24dp"
app:backgroundImage="@color/nc_darkRed" android:background="@drawable/shape_oval"
app:placeholderImage="@drawable/ic_call_end_white_24px" android:backgroundTint="@color/nc_darkRed"
app:roundAsCircle="true" /> android:src="@drawable/ic_call_end_white_24px"
android:contentDescription="@string/nc_call_button_content_description_hangup" />
<com.facebook.drawee.view.SimpleDraweeView <ImageButton
android:id="@+id/callAnswerCameraView" android:id="@+id/callAnswerCameraView"
android:layout_width="60dp" android:layout_width="60dp"
android:layout_height="60dp" android:layout_height="60dp"
android:layout_margin="24dp" android:layout_margin="24dp"
android:background="@drawable/shape_oval"
android:backgroundTint="@color/nc_darkGreen"
android:src="@drawable/ic_videocam_white_24px"
android:visibility="gone" android:visibility="gone"
app:backgroundImage="@color/nc_darkGreen" tools:visibility="visible"
app:placeholderImage="@drawable/ic_videocam_white_24px" android:contentDescription="@string/nc_call_button_content_description_answer_video_call" />
app:roundAsCircle="true"
tools:visibility="visible" />
</LinearLayout> </LinearLayout>
<RelativeLayout <RelativeLayout
@ -111,11 +114,11 @@
</RelativeLayout> </RelativeLayout>
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/avatarImageView" android:id="@+id/avatarImageView"
android:layout_width="@dimen/avatar_size_big" android:layout_width="@dimen/avatar_size_big"
android:layout_height="@dimen/avatar_size_big" android:layout_height="@dimen/avatar_size_big"
android:layout_centerInParent="true" android:layout_centerInParent="true"
app:roundAsCircle="true" /> android:contentDescription="@string/avatar" />
</RelativeLayout> </RelativeLayout>

View File

@ -77,13 +77,13 @@
android:layout_marginTop="@dimen/margin_between_elements" android:layout_marginTop="@dimen/margin_between_elements"
tools:text="Jane Doe" /> tools:text="Jane Doe" />
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/avatar_image" android:id="@+id/avatar_image"
android:layout_width="@dimen/avatar_size_big" android:layout_width="@dimen/avatar_size_big"
android:layout_height="@dimen/avatar_size_big" android:layout_height="@dimen/avatar_size_big"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
apc:roundAsCircle="true" tools:background="@color/hwSecurityRed"
tools:background="@color/hwSecurityRed" /> android:contentDescription="@string/avatar" />
</RelativeLayout> </RelativeLayout>
</com.yarolegovich.mp.MaterialPreferenceCategory> </com.yarolegovich.mp.MaterialPreferenceCategory>

View File

@ -19,7 +19,6 @@
--> -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -31,7 +30,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/avatar_image" android:id="@+id/avatar_image"
android:layout_width="@dimen/avatar_size_big" android:layout_width="@dimen/avatar_size_big"
android:layout_height="@dimen/avatar_size_big" android:layout_height="@dimen/avatar_size_big"
@ -39,9 +38,7 @@
android:layout_marginTop="@dimen/standard_margin" android:layout_marginTop="@dimen/standard_margin"
android:src="@drawable/account_circle_96dp" android:src="@drawable/account_circle_96dp"
android:transitionName="userAvatar.transitionTag" android:transitionName="userAvatar.transitionTag"
app:roundAsCircle="true" android:contentDescription="@string/avatar" />
fresco:failureImage="@drawable/account_circle_96dp"
fresco:placeholderImage="@drawable/account_circle_96dp" />
<androidx.emoji.widget.EmojiTextView <androidx.emoji.widget.EmojiTextView
android:id="@+id/userinfo_fullName" android:id="@+id/userinfo_fullName"

View File

@ -25,7 +25,6 @@
<com.yarolegovich.mp.MaterialPreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <com.yarolegovich.mp.MaterialPreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:apc="http://schemas.android.com/apk/res-auto" xmlns:apc="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:background="@color/white" tools:background="@color/white"
android:id="@+id/settings_screen" android:id="@+id/settings_screen"
@ -119,16 +118,14 @@
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/avatar_image" android:id="@+id/avatar_image"
android:layout_width="@dimen/avatar_size_big" android:layout_width="@dimen/avatar_size_big"
android:layout_height="@dimen/avatar_size_big" android:layout_height="@dimen/avatar_size_big"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:src="@drawable/account_circle_96dp" android:src="@drawable/account_circle_96dp"
android:transitionName="userAvatar.transitionTag" android:transitionName="userAvatar.transitionTag"
apc:roundAsCircle="true" android:contentDescription="@string/avatar" />
fresco:failureImage="@drawable/account_circle_96dp"
fresco:placeholderImage="@drawable/account_circle_96dp" />
<com.yarolegovich.mp.MaterialStandardPreference <com.yarolegovich.mp.MaterialStandardPreference
android:id="@+id/settings_remove_account" android:id="@+id/settings_remove_account"

View File

@ -21,7 +21,6 @@
--> -->
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="72dp" android:layout_height="72dp"
@ -45,7 +44,7 @@
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_centerVertical="true"> android:layout_centerVertical="true">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/user_icon" android:id="@+id/user_icon"
android:layout_width="@dimen/small_item_height" android:layout_width="@dimen/small_item_height"
android:layout_height="@dimen/small_item_height" android:layout_height="@dimen/small_item_height"
@ -55,10 +54,7 @@
android:layout_marginEnd="1dp" android:layout_marginEnd="1dp"
android:layout_marginBottom="1dp" android:layout_marginBottom="1dp"
android:contentDescription="@string/avatar" android:contentDescription="@string/avatar"
android:src="@drawable/account_circle_48dp" android:src="@drawable/account_circle_48dp" />
fresco:placeholderImage="@drawable/account_circle_48dp"
fresco:failureImage="@drawable/account_circle_48dp"
app:roundAsCircle="true"/>
<ImageView <ImageView
android:id="@+id/ticker" android:id="@+id/ticker"

View File

@ -32,13 +32,13 @@
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_marginBottom="2dp"> android:layout_marginBottom="2dp">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@id/messageUserAvatar" android:id="@id/messageUserAvatar"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
app:roundAsCircle="true" /> android:contentDescription="@string/avatar" />
<com.google.android.flexbox.FlexboxLayout <com.google.android.flexbox.FlexboxLayout
android:id="@id/bubble" android:id="@id/bubble"

View File

@ -31,13 +31,13 @@
android:layout_marginEnd="@dimen/standard_margin" android:layout_marginEnd="@dimen/standard_margin"
android:layout_marginBottom="2dp"> android:layout_marginBottom="2dp">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@id/messageUserAvatar" android:id="@id/messageUserAvatar"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
app:roundAsCircle="true" /> android:contentDescription="@string/avatar" />
<com.google.android.flexbox.FlexboxLayout <com.google.android.flexbox.FlexboxLayout
android:id="@id/bubble" android:id="@id/bubble"

View File

@ -28,13 +28,13 @@
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_marginBottom="2dp"> android:layout_marginBottom="2dp">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@id/messageUserAvatar" android:id="@id/messageUserAvatar"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
app:roundAsCircle="true" /> android:contentDescription="@string/avatar" />
<com.google.android.flexbox.FlexboxLayout <com.google.android.flexbox.FlexboxLayout
android:id="@id/bubble" android:id="@id/bubble"

View File

@ -32,13 +32,13 @@
android:layout_marginEnd="14dp" android:layout_marginEnd="14dp"
android:layout_marginBottom="2dp"> android:layout_marginBottom="2dp">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@id/messageUserAvatar" android:id="@id/messageUserAvatar"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginEnd="6dp" android:layout_marginEnd="6dp"
app:roundAsCircle="true" /> android:contentDescription="@string/avatar" />
<com.google.android.flexbox.FlexboxLayout <com.google.android.flexbox.FlexboxLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -64,14 +64,13 @@
app:layout_wrapBefore="true" app:layout_wrapBefore="true"
tools:visibility="gone"> tools:visibility="gone">
<!-- SimpleDraweeView does not support wrap_content for layout_width or layout_height attributes! --> <ImageView
<com.facebook.drawee.view.SimpleDraweeView
android:id="@id/image" android:id="@id/image"
android:layout_width="100dp" android:layout_width="match_parent"
android:layout_height="100dp" android:layout_height="wrap_content"
android:scaleType="fitStart" android:scaleType="fitStart"
app:roundedCornerRadius="6dp" tools:src="@drawable/ic_call_black_24dp"
tools:src="@drawable/ic_call_black_24dp" /> tools:ignore="ContentDescription" />
<ProgressBar <ProgressBar
android:id="@+id/progress_bar" android:id="@+id/progress_bar"
@ -105,14 +104,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:adjustViewBounds="true"> android:adjustViewBounds="true">
<!-- SimpleDraweeView does not support wrap_content for layout_width or layout_height attributes! --> <ImageView
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/contact_photo" android:id="@+id/contact_photo"
android:layout_width="@dimen/small_item_height" android:layout_width="@dimen/small_item_height"
android:layout_height="@dimen/small_item_height" android:layout_height="@dimen/small_item_height"
android:scaleType="fitStart" android:scaleType="fitStart"
app:roundAsCircle="true" tools:src="@drawable/ic_call_black_24dp"
tools:src="@drawable/ic_call_black_24dp" /> tools:ignore="ContentDescription" />
<ProgressBar <ProgressBar
android:id="@+id/contact_progress_bar" android:id="@+id/contact_progress_bar"

View File

@ -30,13 +30,13 @@
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_marginBottom="2dp"> android:layout_marginBottom="2dp">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@id/messageUserAvatar" android:id="@id/messageUserAvatar"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
app:roundAsCircle="true" /> android:contentDescription="@string/avatar" />
<com.google.android.flexbox.FlexboxLayout <com.google.android.flexbox.FlexboxLayout
android:id="@id/bubble" android:id="@id/bubble"

View File

@ -32,13 +32,13 @@
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_marginBottom="2dp"> android:layout_marginBottom="2dp">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@id/messageUserAvatar" android:id="@id/messageUserAvatar"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
app:roundAsCircle="true" /> android:contentDescription="@string/avatar" />
<com.google.android.flexbox.FlexboxLayout <com.google.android.flexbox.FlexboxLayout
android:id="@id/bubble" android:id="@id/bubble"

View File

@ -55,14 +55,13 @@
app:layout_flexGrow="1" app:layout_flexGrow="1"
app:layout_wrapBefore="true"> app:layout_wrapBefore="true">
<!-- SimpleDraweeView does not support wrap_content for layout_width or layout_height attributes! --> <ImageView
<com.facebook.drawee.view.SimpleDraweeView
android:id="@id/image" android:id="@id/image"
android:layout_width="100dp" android:layout_width="match_parent"
android:layout_height="100dp" android:layout_height="wrap_content"
android:scaleType="fitEnd" android:scaleType="fitStart"
app:roundedCornerRadius="6dp" tools:src="@drawable/ic_call_black_24dp"
tools:src="@drawable/ic_call_black_24dp" /> tools:ignore="ContentDescription" />
<ProgressBar <ProgressBar
android:id="@+id/progress_bar" android:id="@+id/progress_bar"
@ -95,14 +94,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:adjustViewBounds="true"> android:adjustViewBounds="true">
<!-- SimpleDraweeView does not support wrap_content for layout_width or layout_height attributes! --> <ImageView
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/contact_photo" android:id="@+id/contact_photo"
android:layout_width="@dimen/small_item_height" android:layout_width="@dimen/small_item_height"
android:layout_height="@dimen/small_item_height" android:layout_height="@dimen/small_item_height"
android:scaleType="fitStart" android:scaleType="fitStart"
app:roundAsCircle="true" tools:src="@drawable/ic_call_black_24dp"
tools:src="@drawable/ic_call_black_24dp" /> tools:ignore="ContentDescription" />
<ProgressBar <ProgressBar
android:id="@+id/contact_progress_bar" android:id="@+id/contact_progress_bar"

View File

@ -17,22 +17,21 @@
~ You should have received a copy of the GNU General Public License ~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>. ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="4dp" android:paddingBottom="4dp"
tools:background="@color/white"> tools:background="@color/white"
tools:ignore="UseCompoundDrawables">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/poll_voter_avatar" android:id="@+id/poll_voter_avatar"
android:layout_width="32dp" android:layout_width="32dp"
android:layout_height="32dp" android:layout_height="32dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_gravity="center" android:layout_gravity="center"
app:roundAsCircle="true" /> android:contentDescription="@string/avatar"/>
<TextView <TextView
android:id="@+id/poll_voter_name" android:id="@+id/poll_voter_name"

View File

@ -19,19 +19,18 @@
--> -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/item_height" android:layout_height="@dimen/item_height"
android:background="?android:attr/selectableItemBackground"> android:background="?android:attr/selectableItemBackground">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/avatar" android:id="@+id/avatar"
android:layout_width="@dimen/avatar_size" android:layout_width="@dimen/avatar_size"
android:layout_height="@dimen/avatar_size" android:layout_height="@dimen/avatar_size"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_margin="@dimen/standard_margin" android:layout_margin="@dimen/standard_margin"
app:roundAsCircle="true" /> android:contentDescription="@string/avatar"/>
<TextView <TextView
android:id="@+id/name" android:id="@+id/name"

View File

@ -19,7 +19,6 @@
--> -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/referenceWrapper" android:id="@+id/referenceWrapper"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -38,14 +37,14 @@
android:id="@+id/referenceName" android:id="@+id/referenceName"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:ellipsize="end"
android:lineSpacingMultiplier="1.2" android:lineSpacingMultiplier="1.2"
android:maxLines="2"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textIsSelectable="false" android:textIsSelectable="false"
android:layout_marginStart="10dp"
android:visibility="gone"
android:ellipsize="end"
android:maxLines="2"
android:textStyle="bold" android:textStyle="bold"
android:visibility="gone"
tools:text="Name of Website" tools:text="Name of Website"
tools:visibility="visible" /> tools:visibility="visible" />
@ -54,13 +53,13 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/referenceName" android:layout_below="@id/referenceName"
android:layout_marginStart="10dp"
android:ellipsize="end"
android:lineSpacingMultiplier="1.2" android:lineSpacingMultiplier="1.2"
android:maxLines="2"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textIsSelectable="false" android:textIsSelectable="false"
android:layout_marginStart="10dp"
android:visibility="gone" android:visibility="gone"
android:ellipsize="end"
android:maxLines="2"
tools:text="Description of Website" tools:text="Description of Website"
tools:visibility="visible" /> tools:visibility="visible" />
@ -69,27 +68,27 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/referenceDescription" android:layout_below="@id/referenceDescription"
android:lineSpacingMultiplier="1.2"
android:textAlignment="viewStart"
android:textIsSelectable="false"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:textColor="@color/medium_emphasis_text"
android:singleLine="true"
android:lines="1"
android:ellipsize="end" android:ellipsize="end"
android:lineSpacingMultiplier="1.2"
android:lines="1"
android:singleLine="true"
android:textAlignment="viewStart"
android:textColor="@color/medium_emphasis_text"
android:textIsSelectable="false"
android:visibility="gone" android:visibility="gone"
tools:text="http://nextcloud.com" tools:text="http://nextcloud.com"
tools:visibility="visible" /> tools:visibility="visible" />
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/referenceThumbImage" android:id="@+id/referenceThumbImage"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="120dp" android:layout_height="120dp"
android:scaleType="fitEnd"
android:layout_below="@id/referenceLink" android:layout_below="@id/referenceLink"
android:layout_marginTop="5dp"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:scaleType="fitEnd"
android:visibility="gone" android:visibility="gone"
app:roundedCornerRadius="6dp" tools:visibility="visible"
tools:visibility="visible"/> tools:ignore="ContentDescription" />
</RelativeLayout> </RelativeLayout>

View File

@ -93,13 +93,12 @@
android:textSize="@dimen/two_line_primary_text_size" android:textSize="@dimen/two_line_primary_text_size"
tools:text="filename.md" /> tools:text="filename.md" />
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/file_icon" android:id="@+id/file_icon"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/standard_margin" android:layout_marginEnd="@dimen/standard_margin"
app:actualImageScaleType="fitCenter" tools:ignore="ContentDescription" />
app:placeholderImageScaleType="fitCenter" />
</RelativeLayout> </RelativeLayout>

View File

@ -48,19 +48,19 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_toStartOf="@id/checkedImageView" android:layout_toStartOf="@id/checkedImageView"
android:layout_toEndOf="@id/avatar_drawee_view" android:layout_toEndOf="@id/avatar_view"
android:ellipsize="end" android:ellipsize="end"
android:lines="1" android:lines="1"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textAppearance="@style/ListItem" android:textAppearance="@style/ListItem"
tools:text="Jane Doe" /> tools:text="Jane Doe" />
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/avatar_drawee_view" android:id="@+id/avatar_view"
android:layout_width="@dimen/avatar_size" android:layout_width="@dimen/avatar_size"
android:layout_height="@dimen/avatar_size" android:layout_height="@dimen/avatar_size"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/standard_margin" android:layout_marginEnd="@dimen/standard_margin"
app:roundAsCircle="true" /> android:contentDescription="@string/avatar" />
</RelativeLayout> </RelativeLayout>

View File

@ -29,7 +29,7 @@
android:orientation="vertical"> android:orientation="vertical">
<com.elyeproj.loaderviewlibrary.LoaderTextView <com.elyeproj.loaderviewlibrary.LoaderTextView
android:id="@+id/simple_drawee_view" android:id="@+id/loader_text_view"
android:layout_width="@dimen/avatar_size" android:layout_width="@dimen/avatar_size"
android:layout_height="@dimen/avatar_size" android:layout_height="@dimen/avatar_size"
android:layout_centerVertical="true" android:layout_centerVertical="true"
@ -42,7 +42,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_toEndOf="@id/simple_drawee_view" android:layout_toEndOf="@id/loader_text_view"
app:custom_color="@color/nc_shimmer_default_color" /> app:custom_color="@color/nc_shimmer_default_color" />
</RelativeLayout> </RelativeLayout>

View File

@ -26,15 +26,14 @@
android:layout_marginBottom="@dimen/standard_half_margin" android:layout_marginBottom="@dimen/standard_half_margin"
android:layout_marginTop="@dimen/standard_margin"> android:layout_marginTop="@dimen/standard_margin">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/avatar_drawee_view" android:id="@+id/avatar_view"
android:layout_width="@dimen/small_item_height" android:layout_width="@dimen/small_item_height"
android:layout_height="@dimen/small_item_height" android:layout_height="@dimen/small_item_height"
android:layout_marginStart="@dimen/standard_margin" android:layout_marginStart="@dimen/standard_margin"
android:contentDescription="@null" android:contentDescription="@null"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
app:roundAsCircle="true" />
<com.vanniktech.emoji.EmojiEditText <com.vanniktech.emoji.EmojiEditText
android:id="@+id/participant_status_emoji" android:id="@+id/participant_status_emoji"
@ -54,8 +53,8 @@
android:layout_height="18dp" android:layout_height="18dp"
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
android:contentDescription="@string/nc_account_chooser_active_user" android:contentDescription="@string/nc_account_chooser_active_user"
app:layout_constraintBottom_toBottomOf="@+id/avatar_drawee_view" app:layout_constraintBottom_toBottomOf="@+id/avatar_view"
app:layout_constraintEnd_toEndOf="@+id/avatar_drawee_view" app:layout_constraintEnd_toEndOf="@+id/avatar_view"
tools:src="@drawable/emoji_one_category_smileysandpeople"/> tools:src="@drawable/emoji_one_category_smileysandpeople"/>
<androidx.emoji.widget.EmojiTextView <androidx.emoji.widget.EmojiTextView
@ -68,8 +67,8 @@
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textAppearance="?android:attr/textAppearanceListItem" android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="@color/conversation_item_header" android:textColor="@color/conversation_item_header"
app:layout_constraintStart_toEndOf="@id/avatar_drawee_view" app:layout_constraintStart_toEndOf="@id/avatar_view"
app:layout_constraintTop_toTopOf="@+id/avatar_drawee_view" app:layout_constraintTop_toTopOf="@+id/avatar_view"
tools:text="Jane Doe" /> tools:text="Jane Doe" />
<androidx.emoji.widget.EmojiTextView <androidx.emoji.widget.EmojiTextView

View File

@ -3,6 +3,8 @@
~ ~
~ @author Mario Danic ~ @author Mario Danic
~ @author Andy Scherzinger ~ @author Andy Scherzinger
~ @author Tim Krüger
~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com> ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
~ ~
@ -38,12 +40,11 @@
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/double_margin_between_elements"> android:layout_marginEnd="@dimen/double_margin_between_elements">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@id/dialogAvatar" android:id="@id/dialogAvatar"
android:layout_width="@dimen/small_item_height" android:layout_width="@dimen/small_item_height"
android:layout_height="@dimen/small_item_height" android:layout_height="@dimen/small_item_height"
android:contentDescription="@null" android:contentDescription="@null" />
app:roundAsCircle="true" />
<ImageView <ImageView
android:id="@+id/favoriteConversationImageView" android:id="@+id/favoriteConversationImageView"

View File

@ -41,8 +41,7 @@
android:layout_height="@dimen/small_item_height" android:layout_height="@dimen/small_item_height"
android:importantForAccessibility="no" android:importantForAccessibility="no"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
app:roundAsCircle="true" />
<TextView <TextView
android:layout_width="0dp" android:layout_width="0dp"

View File

@ -3,6 +3,8 @@
~ ~
~ @author Mario Danic ~ @author Mario Danic
~ @author Andy Scherzinger ~ @author Andy Scherzinger
~ @author Tim Krüger
~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com> ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
~ ~
@ -35,14 +37,13 @@
android:layout_margin="@dimen/double_margin_between_elements" android:layout_margin="@dimen/double_margin_between_elements"
tools:background="@color/white"> tools:background="@color/white">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/thumbnail" android:id="@+id/thumbnail"
android:layout_width="@dimen/small_item_height" android:layout_width="@dimen/small_item_height"
android:layout_height="@dimen/small_item_height" android:layout_height="@dimen/small_item_height"
android:importantForAccessibility="no" android:importantForAccessibility="no"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
app:roundAsCircle="true" />
<androidx.emoji.widget.EmojiTextView <androidx.emoji.widget.EmojiTextView
android:id="@+id/conversation_title" android:id="@+id/conversation_title"

View File

@ -22,7 +22,7 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:fresco="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@ -40,14 +40,12 @@
app:layout_flexGrow="1" app:layout_flexGrow="1"
app:layout_wrapBefore="true"> app:layout_wrapBefore="true">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/image" android:id="@+id/image"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="4dp" android:padding="4dp"
app:placeholderImageScaleType="fitCenter" tools:ignore="ContentDescription" />
fresco:actualImageScaleType="centerCrop"
fresco:roundedCornerRadius="4dp" />
<ProgressBar <ProgressBar
android:id="@+id/progress_bar" android:id="@+id/progress_bar"

View File

@ -39,17 +39,14 @@
app:layout_flexGrow="1" app:layout_flexGrow="1"
app:layout_wrapBefore="true"> app:layout_wrapBefore="true">
<com.facebook.drawee.view.SimpleDraweeView <ImageView
xmlns:fresco="http://schemas.android.com/apk/res-auto"
android:id="@+id/file_image" android:id="@+id/file_image"
android:layout_width="@dimen/mediatab_file_icon_size" android:layout_width="@dimen/mediatab_file_icon_size"
android:layout_height="@dimen/mediatab_file_icon_size" android:layout_height="@dimen/mediatab_file_icon_size"
android:padding="4dp" android:padding="4dp"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:placeholderImageScaleType="fitCenter" tools:src="@drawable/ic_call_black_24dp"
fresco:actualImageScaleType="centerCrop" tools:ignore="ContentDescription" />
fresco:roundedCornerRadius="4dp"
tools:src="@drawable/ic_call_black_24dp"/>
<ProgressBar <ProgressBar
android:id="@+id/progress_bar" android:id="@+id/progress_bar"

View File

@ -75,7 +75,5 @@
<color name="grey_200">#818181</color> <color name="grey_200">#818181</color>
<color name="dialog_background">#353535</color> <color name="dialog_background">#353535</color>
<color name="vote_dialog_background">#424242</color>
</resources> </resources>

View File

@ -105,8 +105,5 @@
<!-- this is just a helper for status icon background because getting the background color of a dialog is not <!-- this is just a helper for status icon background because getting the background color of a dialog is not
possible?! don't use this to set the background of dialogs --> possible?! don't use this to set the background of dialogs -->
<color name="dialog_background">#FFFFFF</color> <color name="dialog_background">#FFFFFF</color>
<color name="vote_dialog_background">#FFFFFF</color>
</resources> </resources>

View File

@ -2,6 +2,8 @@
~ Nextcloud Talk application ~ Nextcloud Talk application
~ ~
~ @author Mario Danic ~ @author Mario Danic
~ @author Tim Krüger
~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com> ~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
~ ~
~ This program is free software: you can redistribute it and/or modify ~ This program is free software: you can redistribute it and/or modify
@ -67,6 +69,8 @@
<dimen name="call_self_video_short_side_length">80dp</dimen> <dimen name="call_self_video_short_side_length">80dp</dimen>
<dimen name="call_grid_item_min_height">180dp</dimen> <dimen name="call_grid_item_min_height">180dp</dimen>
<dimen name="call_controls_height">110dp</dimen> <dimen name="call_controls_height">110dp</dimen>
<dimen name="call_controls_padding_horizontal">10dp</dimen>
<dimen name="call_controls_margin_horizontal">10dp</dimen>
<dimen name="call_participant_progress_bar_size">48dp</dimen> <dimen name="call_participant_progress_bar_size">48dp</dimen>
<dimen name="call_self_participant_progress_bar_size">48dp</dimen> <dimen name="call_self_participant_progress_bar_size">48dp</dimen>
<dimen name="zero">0dp</dimen> <dimen name="zero">0dp</dimen>

View File

@ -4,6 +4,8 @@
~ @author Mario Danic ~ @author Mario Danic
~ @author Andy Scherzinger ~ @author Andy Scherzinger
~ @author Marcel Hibbe ~ @author Marcel Hibbe
~ @author Tim Krüger
~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> ~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com> ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
@ -211,6 +213,14 @@
<string name="nc_call_state_with_phone">%1$s with phone</string> <string name="nc_call_state_with_phone">%1$s with phone</string>
<string name="nc_call_state_with_video">%1$s with video</string> <string name="nc_call_state_with_video">%1$s with video</string>
<string name="nc_missed_call">You missed a call from %s</string> <string name="nc_missed_call">You missed a call from %s</string>
<string name="nc_call_button_content_description_pip">Open picture in picture mode</string>
<string name="nc_call_button_content_description_audio_output">Change audio output</string>
<string name="nc_call_button_content_description_camera">Toggle camera</string>
<string name="nc_call_button_content_description_microphone">Toggle microphone</string>
<string name="nc_call_button_content_description_hangup">Hangup</string>
<string name="nc_call_button_content_description_answer_voice_only">Answer as voice call only</string>
<string name="nc_call_button_content_description_answer_video_call">Answer as video call</string>
<string name="nc_call_button_content_description_switch_to_self_vide">Switch to self video</string>
<!-- Picture in Picture --> <!-- Picture in Picture -->
<string name="nc_pip_microphone_mute">Mute microphone</string> <string name="nc_pip_microphone_mute">Mute microphone</string>

View File

@ -1,2 +1,2 @@
DO NOT TOUCH; GENERATED BY DRONE DO NOT TOUCH; GENERATED BY DRONE
<span class="mdl-layout-title">Lint Report: 1 error and 112 warnings</span> <span class="mdl-layout-title">Lint Report: 112 warnings</span>