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

View File

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

View File

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

View File

@ -28,8 +28,6 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.media.AudioAttributes
import android.media.MediaPlayer
import android.os.Build
@ -41,24 +39,18 @@ import android.view.View
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import autodagger.AutoInjector
import com.facebook.common.executors.UiThreadImmediateExecutorService
import com.facebook.common.references.CloseableReference
import com.facebook.datasource.DataSource
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber
import com.facebook.imagepipeline.image.CloseableImage
import com.nextcloud.talk.R
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.CallNotificationActivityBinding
import com.nextcloud.talk.extensions.loadAvatar
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.models.json.participants.ParticipantsOverall
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.DoNotDisturbUtils.shouldPlaySound
import com.nextcloud.talk.utils.NotificationUtils
import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri
@ -75,6 +67,7 @@ import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.call_item.*
import okhttp3.Cache
import org.parceler.Parcels
import java.io.IOException
@ -356,7 +349,7 @@ class CallNotificationActivity : CallBaseActivity() {
private fun setUpAfterConversationIsKnown() {
binding!!.conversationNameTextView.text = currentConversation!!.displayName
if (currentConversation!!.type === Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
setAvatarForOneToOneCall()
avatarImageView.loadAvatar(userBeingCalled!!, currentConversation!!.name!!)
} else {
binding!!.avatarImageView.setImageResource(R.drawable.ic_circular_group)
}
@ -364,34 +357,6 @@ class CallNotificationActivity : CallBaseActivity() {
showAnswerControls()
}
@Suppress("MagicNumber")
private fun setAvatarForOneToOneCall() {
val imageRequest = DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar(
userBeingCalled!!.baseUrl,
currentConversation!!.name,
true
)
)
val imagePipeline = Fresco.getImagePipeline()
val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null)
dataSource.subscribe(
object : BaseBitmapDataSubscriber() {
override fun onNewResultImpl(bitmap: Bitmap?) {
binding!!.avatarImageView.hierarchy.setImage(
BitmapDrawable(resources, bitmap), 100f,
true
)
}
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage?>>) {
Log.e(TAG, "failed to load avatar")
}
},
UiThreadImmediateExecutorService.getInstance()
)
}
private fun endMediaNotifications() {
if (mediaPlayer != null) {
if (mediaPlayer!!.isPlaying) {

View File

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

View File

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

View File

@ -34,7 +34,7 @@ class ReactionsAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReactionsViewHolder {
val itemBinding = ReactionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ReactionsViewHolder(itemBinding, user?.baseUrl)
return ReactionsViewHolder(itemBinding, user)
}
override fun onBindViewHolder(holder: ReactionsViewHolder, position: Int) {

View File

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

View File

@ -27,15 +27,12 @@ import android.net.Uri;
import android.text.TextUtils;
import android.view.View;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.interfaces.DraweeController;
import com.nextcloud.talk.R;
import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.databinding.AccountItemBinding;
import com.nextcloud.talk.extensions.ImageViewExtensionsKt;
import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.ui.theme.ViewThemeUtils;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.DisplayUtils;
import java.util.List;
import java.util.regex.Pattern;
@ -108,7 +105,6 @@ public class AdvancedUserItem extends AbstractFlexibleItem<AdvancedUserItem.User
@Override
public void bindViewHolder(FlexibleAdapter adapter, UserItemViewHolder holder, int position, List payloads) {
holder.binding.userIcon.setController(null);
if (adapter.hasFilter()) {
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 &&
user.getBaseUrl().startsWith("http://") ||
user.getBaseUrl().startsWith("https://")) {
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);
ImageViewExtensionsKt.loadAvatar(holder.binding.userIcon, user, participant.getCalculatedActorId(), true);
}
}

View File

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

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.view.View;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.interfaces.DraweeController;
import com.nextcloud.talk.R;
import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.extensions.ImageViewExtensionsKt;
import com.nextcloud.talk.models.json.mention.Mention;
import com.nextcloud.talk.models.json.status.StatusType;
import com.nextcloud.talk.ui.StatusDrawable;
import com.nextcloud.talk.ui.theme.ViewThemeUtils;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.DisplayUtils;
import java.util.List;
@ -151,34 +149,22 @@ public class MentionAutocompleteItem extends AbstractFlexibleItem<ParticipantIte
if (SOURCE_CALLS.equals(source)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
holder.binding.avatarDraweeView.getHierarchy().setPlaceholderImage(
DisplayUtils.getRoundedDrawable(
viewThemeUtils.talk.themePlaceholderAvatar(holder.binding.avatarDraweeView,
R.drawable.ic_avatar_group)));
ImageViewExtensionsKt.loadAvatar(
holder.binding.avatarView,
viewThemeUtils.talk.themePlaceholderAvatar(
holder.binding.avatarView,
R.drawable.ic_avatar_group
)
);
} else {
holder.binding.avatarDraweeView.setImageResource(R.drawable.ic_circular_group);
ImageViewExtensionsKt.loadAvatar(holder.binding.avatarView, R.drawable.ic_circular_group);
}
} else {
String avatarId = objectId;
String avatarUrl = ApiUtils.getUrlForAvatar(currentUser.getBaseUrl(),
avatarId, true);
if (SOURCE_GUESTS.equals(source)) {
avatarId = displayName;
avatarUrl = ApiUtils.getUrlForGuestAvatar(
currentUser.getBaseUrl(),
avatarId,
false);
}
holder.binding.avatarDraweeView.setController(null);
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
.setOldController(holder.binding.avatarDraweeView.getController())
.setAutoPlayAnimations(true)
.setImageRequest(DisplayUtils.getImageRequestForUrl(avatarUrl))
.build();
holder.binding.avatarDraweeView.setController(draweeController);
ImageViewExtensionsKt.loadAvatar(holder.binding.avatarView, currentUser, avatarId, true);
}
drawStatus(holder);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
*
* @author Andy Scherzinger
* @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>
*
* This program is free software: you can redistribute it and/or modify
@ -23,9 +23,9 @@
package com.nextcloud.talk.adapters.messages;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.facebook.drawee.view.SimpleDraweeView;
import com.google.android.material.card.MaterialCardView;
import com.nextcloud.talk.R;
import com.nextcloud.talk.databinding.ItemCustomIncomingPreviewMessageBinding;
@ -74,7 +74,7 @@ public class IncomingPreviewMessageViewHolder extends PreviewMessageViewHolder {
}
@Override
public SimpleDraweeView getPreviewContactPhoto() {
public ImageView getPreviewContactPhoto() {
return binding.contactPhoto;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@ import android.text.Editable;
import android.text.Spanned;
import android.widget.EditText;
import com.facebook.widget.text.span.BetterImageSpan;
import third.parties.fresco.BetterImageSpan;
import com.nextcloud.talk.R;
import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.models.json.mention.Mention;

View File

@ -5,7 +5,7 @@
* @author Marcel Hibbe
* @author Andy Scherzinger
* @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-2022 Marcel Hibbe <dev@mhibbe.de>
* 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.Resources
import android.database.Cursor
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.media.MediaPlayer
import android.media.MediaRecorder
import android.net.Uri
@ -78,7 +79,7 @@ import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.content.PermissionChecker
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import androidx.core.graphics.drawable.toBitmap
import androidx.core.widget.doAfterTextChanged
import androidx.emoji.text.EmojiCompat
import androidx.emoji.widget.EmojiTextView
@ -90,15 +91,13 @@ import androidx.work.OneTimeWorkRequest
import androidx.work.WorkInfo
import androidx.work.WorkManager
import autodagger.AutoInjector
import coil.imageLoader
import coil.load
import coil.request.ImageRequest
import coil.target.Target
import coil.transform.CircleCropTransformation
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.facebook.common.executors.UiThreadImmediateExecutorService
import com.facebook.common.references.CloseableReference
import com.facebook.datasource.DataSource
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber
import com.facebook.imagepipeline.image.CloseableImage
import com.google.android.flexbox.FlexboxLayout
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.talk.BuildConfig
@ -134,6 +133,7 @@ import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ControllerChatBinding
import com.nextcloud.talk.events.UserMentionClickEvent
import com.nextcloud.talk.events.WebSocketCommunicationEvent
import com.nextcloud.talk.extensions.loadAvatarOrImagePreview
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
import com.nextcloud.talk.jobs.ShareOperationWorker
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
@ -165,7 +165,6 @@ import com.nextcloud.talk.utils.ConductorRemapping
import com.nextcloud.talk.utils.ConductorRemapping.remapChatController
import com.nextcloud.talk.utils.ContactUtils
import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.FileUtils
import com.nextcloud.talk.utils.ImageEmojiEditText
import com.nextcloud.talk.utils.MagicCharPolicy
@ -462,42 +461,48 @@ class ChatController(args: Bundle) :
private fun loadAvatarForStatusBar() {
if (isOneToOneConversation() && activity != null) {
val imageRequest = DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar(
conversationUser?.baseUrl,
currentConversation?.name,
true
),
conversationUser!!
val url = ApiUtils.getUrlForAvatar(
conversationUser!!.baseUrl,
currentConversation!!.name,
true
)
val target = object : Target {
val imagePipeline = Fresco.getImagePipeline()
val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null)
private fun setIcon(drawable: Drawable?) {
dataSource.subscribe(
object : BaseBitmapDataSubscriber() {
override fun onNewResultImpl(bitmap: Bitmap?) {
if (actionBar != null && bitmap != null && resources != null) {
val avatarSize = (actionBar?.height!! / TOOLBAR_AVATAR_RATIO).roundToInt()
if (avatarSize > 0) {
val bitmapResized = Bitmap.createScaledBitmap(bitmap, avatarSize, avatarSize, false)
actionBar?.let {
val avatarSize = (it.height / TOOLBAR_AVATAR_RATIO).roundToInt()
val roundedBitmapDrawable =
RoundedBitmapDrawableFactory.create(resources!!, bitmapResized)
roundedBitmapDrawable.isCircular = true
roundedBitmapDrawable.setAntiAlias(true)
actionBar?.setIcon(roundedBitmapDrawable)
} else {
Log.d(TAG, "loadAvatarForStatusBar avatarSize <= 0")
}
if (drawable != null && avatarSize > 0) {
val bitmap = drawable.toBitmap(avatarSize, avatarSize)
it.setIcon(BitmapDrawable(resources, bitmap))
} else {
Log.d(TAG, "loadAvatarForStatusBar avatarSize <= 0")
}
}
}
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage>>) {
// unused atm
}
},
UiThreadImmediateExecutorService.getInstance()
override fun onStart(placeholder: Drawable?) {
this.setIcon(placeholder)
}
override fun onSuccess(result: Drawable) {
this.setIcon(result)
}
}
val credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)
context.imageLoader.enqueue(
ImageRequest.Builder(context)
.data(url)
.addHeader("Authorization", credentials)
.placeholder(R.drawable.ic_user)
.transformations(CircleCropTransformation())
.crossfade(true)
.target(target)
.build()
)
}
}
@ -617,14 +622,8 @@ class ChatController(args: Bundle) :
adapter = TalkMessagesListAdapter(
senderId,
messageHolders,
ImageLoader { imageView, url, payload ->
val draweeController = Fresco.newDraweeControllerBuilder()
.setImageRequest(DisplayUtils.getImageRequestForUrl(url, conversationUser))
.setControllerListener(DisplayUtils.getImageControllerListener(imageView))
.setOldController(imageView.controller)
.setAutoPlayAnimations(true)
.build()
imageView.controller = draweeController
ImageLoader { imageView, url, _ ->
imageView.loadAvatarOrImagePreview(url!!, conversationUser, placeholder = payload as? Drawable)
},
this
)
@ -1150,14 +1149,10 @@ class ChatController(args: Bundle) :
}
private fun isRecordAudioPermissionGranted(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return PermissionChecker.checkSelfPermission(
context,
Manifest.permission.RECORD_AUDIO
) == PermissionChecker.PERMISSION_GRANTED
} else {
true
}
return PermissionChecker.checkSelfPermission(
context,
Manifest.permission.RECORD_AUDIO
) == PermissionChecker.PERMISSION_GRANTED
}
private fun startAudioRecording(file: String) {

View File

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

View File

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

View File

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

View File

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

View File

@ -157,18 +157,13 @@ class SettingsController : BaseController(R.layout.controller_settings) {
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(
String.format(
Locale.getDefault(),
resources!!.getString(R.string.nc_settings_screen_lock_desc),
resources!!.getString(R.string.nc_app_product_name)
)
binding.settingsScreenLock.setSummary(
String.format(
Locale.getDefault(),
resources!!.getString(R.string.nc_settings_screen_lock_desc),
resources!!.getString(R.string.nc_app_product_name)
)
}
)
setupPrivacyUrl()
setupSourceCodeUrl()
@ -662,10 +657,8 @@ class SettingsController : BaseController(R.layout.controller_settings) {
appPreferences.isKeyboardIncognito
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
(binding.settingsIncognitoKeyboard.findViewById<View>(R.id.mp_checkable) as Checkable).isChecked =
appPreferences.isKeyboardIncognito
}
(binding.settingsIncognitoKeyboard.findViewById<View>(R.id.mp_checkable) as Checkable).isChecked =
appPreferences.isKeyboardIncognito
if (CapabilitiesUtilNew.isReadStatusAvailable(userManager.currentUser.blockingGet())) {
(binding.settingsReadPrivacy.findViewById<View>(R.id.mp_checkable) as Checkable).isChecked =
@ -679,29 +672,27 @@ class SettingsController : BaseController(R.layout.controller_settings) {
}
private fun setupScreenLockSetting() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
if (keyguardManager.isKeyguardSecure) {
binding.settingsScreenLock.isEnabled = true
binding.settingsScreenLockTimeout.isEnabled = true
(binding.settingsScreenLock.findViewById<View>(R.id.mp_checkable) as Checkable).isChecked =
appPreferences.isScreenLocked
binding.settingsScreenLockTimeout.isEnabled = appPreferences.isScreenLocked
if (appPreferences.isScreenLocked) {
binding.settingsScreenLockTimeout.alpha = ENABLED_ALPHA
} else {
binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA
}
binding.settingsScreenLock.alpha = ENABLED_ALPHA
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
if (keyguardManager.isKeyguardSecure) {
binding.settingsScreenLock.isEnabled = true
binding.settingsScreenLockTimeout.isEnabled = true
(binding.settingsScreenLock.findViewById<View>(R.id.mp_checkable) as Checkable).isChecked =
appPreferences.isScreenLocked
binding.settingsScreenLockTimeout.isEnabled = appPreferences.isScreenLocked
if (appPreferences.isScreenLocked) {
binding.settingsScreenLockTimeout.alpha = ENABLED_ALPHA
} else {
binding.settingsScreenLock.isEnabled = false
binding.settingsScreenLockTimeout.isEnabled = false
appPreferences.removeScreenLock()
appPreferences.removeScreenLockTimeout()
(binding.settingsScreenLock.findViewById<View>(R.id.mp_checkable) as Checkable).isChecked = false
binding.settingsScreenLock.alpha = DISABLED_ALPHA
binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA
}
binding.settingsScreenLock.alpha = ENABLED_ALPHA
} else {
binding.settingsScreenLock.isEnabled = false
binding.settingsScreenLockTimeout.isEnabled = false
appPreferences.removeScreenLock()
appPreferences.removeScreenLockTimeout()
(binding.settingsScreenLock.findViewById<View>(R.id.mp_checkable) as Checkable).isChecked = false
binding.settingsScreenLock.alpha = DISABLED_ALPHA
binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA
}
}
@ -805,9 +796,7 @@ class SettingsController : BaseController(R.layout.controller_settings) {
private inner class ScreenLockTimeoutListener : OnPreferenceValueChangedListener<String?> {
override fun onChanged(newValue: String?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
SecurityUtils.createKey(appPreferences.screenLockTimeout)
}
SecurityUtils.createKey(appPreferences.screenLockTimeout)
}
}

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!!)
)
}
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()
notificationInfoBundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!)
// could be an ID or a TOKEN
@ -526,7 +524,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
notificationUser.id,
false
) else ApiUtils.getUrlForGuestAvatar(baseUrl, notificationUser.name, false)
person.setIcon(loadAvatarSync(avatarUrl))
person.setIcon(loadAvatarSync(avatarUrl, context!!))
}
notificationBuilder.setStyle(getStyle(person.build(), style))
}
@ -694,7 +692,6 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
.subscribe(object : Observer<ParticipantsOverall> {
override fun onSubscribe(d: Disposable) = Unit
@RequiresApi(Build.VERSION_CODES.M)
override fun onNext(participantsOverall: ParticipantsOverall) {
val participantList: List<Participant> = participantsOverall.ocs!!.data!!
hasParticipantsInCall = participantList.isNotEmpty()
@ -726,7 +723,6 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
Log.e(TAG, "Error in getPeersForCall", e)
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onComplete() {
if (isCallNotificationVisible) {
@ -821,7 +817,6 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
return PendingIntent.getActivity(context, requestCode, intent, intentFlag)
}
@RequiresApi(Build.VERSION_CODES.M)
private fun isCallNotificationVisible(decryptedPushMessage: DecryptedPushMessage): Boolean {
var isVisible = false

View File

@ -295,7 +295,7 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
false
}
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
else -> {
if (PermissionChecker.checkSelfPermission(
context,
Manifest.permission.WRITE_EXTERNAL_STORAGE
@ -308,10 +308,6 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
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
)
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
else -> {
controller.requestPermissions(
arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE
@ -333,8 +329,6 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
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.text.TextUtils
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.interfaces.DraweeController
import android.widget.ImageView
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.PollResultVoterItemBinding
import com.nextcloud.talk.extensions.loadAvatar
import com.nextcloud.talk.extensions.loadGuestAvatar
import com.nextcloud.talk.polls.model.PollDetails
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
class PollResultVoterViewHolder(
private val user: User,
@ -46,45 +45,19 @@ class PollResultVoterViewHolder(
binding.root.setOnClickListener { clickListener.onClick() }
binding.pollVoterName.text = item.details.actorDisplayName
binding.pollVoterAvatar.controller = getAvatarDraweeController(item.details)
loadAvatar(item.details, binding.pollVoterAvatar)
viewThemeUtils.dialog.colorDialogSupportingText(binding.pollVoterName)
}
private fun getAvatarDraweeController(pollDetail: PollDetails): DraweeController? {
var draweeController: DraweeController? = null
private fun loadAvatar(pollDetail: PollDetails, avatar: ImageView) {
if (pollDetail.actorType == "guests") {
var displayName = NextcloudTalkApplication.sharedApplication?.resources?.getString(R.string.nc_guest)
if (!TextUtils.isEmpty(pollDetail.actorDisplayName)) {
displayName = pollDetail.actorDisplayName!!
}
draweeController = Fresco.newDraweeControllerBuilder()
.setAutoPlayAnimations(true)
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForGuestAvatar(
user.baseUrl,
displayName,
false
),
user
)
)
.build()
avatar.loadGuestAvatar(user, displayName!!, false)
} else if (pollDetail.actorType == "users") {
draweeController = Fresco.newDraweeControllerBuilder()
.setAutoPlayAnimations(true)
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar(
user.baseUrl,
pollDetail.actorId,
false
),
user
)
)
.build()
avatar.loadAvatar(user, pollDetail.actorId!!, false)
}
return draweeController
}
}

View File

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

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_SYSTEM_NOTIFICATION_ID
import io.reactivex.Observer
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
@ -159,19 +160,25 @@ class DirectReplyReceiver : BroadcastReceiver() {
.extractMessagingStyleFromNotification(previousNotification)
// Add reply
val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false)
val me = Person.Builder()
.setName(currentUser.displayName)
.setIcon(NotificationUtils.loadAvatarSync(avatarUrl))
.build()
val message = NotificationCompat.MessagingStyle.Message(reply, System.currentTimeMillis(), me)
previousStyle?.addMessage(message)
Single.fromCallable {
val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false)
val me = Person.Builder()
.setName(currentUser.displayName)
.setIcon(NotificationUtils.loadAvatarSync(avatarUrl, context))
.build()
val message = NotificationCompat.MessagingStyle.Message(reply, System.currentTimeMillis(), me)
previousStyle?.addMessage(message)
// Set the updated style
previousBuilder.setStyle(previousStyle)
// Set the updated style
previousBuilder.setStyle(previousStyle)
// Update the active notification.
NotificationManagerCompat.from(context).notify(systemNotificationId!!, previousBuilder.build())
// Check if notification still exists
if (findActiveNotification(systemNotificationId!!) != null) {
NotificationManagerCompat.from(context).notify(systemNotificationId!!, previousBuilder.build())
}
}
.subscribeOn(Schedulers.io())
.subscribe()
}
companion object {

View File

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

View File

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

View File

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

View File

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

View File

@ -23,29 +23,20 @@
package com.nextcloud.talk.shareditems.adapters
import android.content.Context
import android.net.Uri
import android.util.Log
import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.controller.BaseControllerListener
import com.facebook.drawee.controller.ControllerListener
import com.facebook.drawee.interfaces.DraweeController
import com.facebook.drawee.view.SimpleDraweeView
import com.facebook.imagepipeline.common.RotationOptions
import com.facebook.imagepipeline.image.ImageInfo
import com.facebook.imagepipeline.request.ImageRequestBuilder
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.extensions.loadImage
import com.nextcloud.talk.shareditems.model.SharedDeckCardItem
import com.nextcloud.talk.shareditems.model.SharedFileItem
import com.nextcloud.talk.shareditems.model.SharedItem
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.shareditems.model.SharedLocationItem
import com.nextcloud.talk.shareditems.model.SharedOtherItem
import com.nextcloud.talk.shareditems.model.SharedPollItem
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.FileViewerUtils
abstract class SharedItemsViewHolder(
@ -58,21 +49,21 @@ abstract class SharedItemsViewHolder(
private val TAG = SharedItemsViewHolder::class.simpleName
}
abstract val image: SimpleDraweeView
abstract val image: ImageView
abstract val clickTarget: View
abstract val progressBar: ProgressBar
private val authHeader = mapOf(
Pair(
"Authorization",
ApiUtils.getCredentials(user.username, user.token)
)
)
open fun onBind(item: SharedFileItem) {
image.hierarchy.setPlaceholderImage(viewThemeUtils.talk.getPlaceholderImage(image.context, item.mimeType))
val placeholder = viewThemeUtils.talk.getPlaceholderImage(image.context, item.mimeType)
if (item.previewAvailable) {
image.controller = configurePreview(item)
image.loadImage(
item.previewLink,
user,
placeholder
)
} else {
image.setImageDrawable(placeholder)
}
/*
@ -105,28 +96,6 @@ abstract class SharedItemsViewHolder(
)
}
private fun configurePreview(item: SharedFileItem): DraweeController {
val imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(item.previewLink))
.setProgressiveRenderingEnabled(true)
.setRotationOptions(RotationOptions.autoRotate())
.disableDiskCache()
.setHeaders(authHeader)
.build()
val listener: ControllerListener<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: SharedLocationItem) {}

View File

@ -33,8 +33,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.interfaces.DraweeController;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.nextcloud.talk.activities.MainActivity;
import com.nextcloud.talk.adapters.items.AdvancedUserItem;
@ -42,6 +40,7 @@ import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.databinding.DialogChooseAccountBinding;
import com.nextcloud.talk.extensions.ImageViewExtensionsKt;
import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.models.json.status.Status;
import com.nextcloud.talk.models.json.status.StatusOverall;
@ -132,22 +131,10 @@ public class ChooseAccountDialogFragment extends DialogFragment {
if (user.getBaseUrl() != null &&
(user.getBaseUrl().startsWith("http://") || user.getBaseUrl().startsWith("https://"))) {
binding.currentAccount.userIcon.setVisibility(View.VISIBLE);
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
.setOldController(binding.currentAccount.userIcon.getController())
.setAutoPlayAnimations(true)
.setImageRequest(DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar(
user.getBaseUrl(),
user.getUserId(),
false)))
.build();
binding.currentAccount.userIcon.setController(draweeController);
ImageViewExtensionsKt.loadAvatar(binding.currentAccount.userIcon, user, user.getUserId(), true);
} else {
binding.currentAccount.userIcon.setVisibility(View.INVISIBLE);
}
loadCurrentStatus(user);
}
}

View File

@ -33,8 +33,6 @@ import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.LinearLayoutManager
import autodagger.AutoInjector
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.interfaces.DraweeController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.adapters.items.AdvancedUserItem
@ -42,11 +40,10 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.DialogChooseAccountShareToBinding
import com.nextcloud.talk.extensions.loadAvatar
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import java.net.CookieManager
@ -97,21 +94,7 @@ class ChooseAccountShareToDialogFragment : DialogFragment() {
if (user.baseUrl != null &&
(user.baseUrl!!.startsWith("http://") || user.baseUrl!!.startsWith("https://"))
) {
binding!!.currentAccount.userIcon.visibility = View.VISIBLE
val draweeController: DraweeController = Fresco.newDraweeControllerBuilder()
.setOldController(binding!!.currentAccount.userIcon.controller)
.setAutoPlayAnimations(true)
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar(
user.baseUrl,
user.userId,
false
)
)
)
.build()
binding!!.currentAccount.userIcon.controller = draweeController
binding!!.currentAccount.userIcon.loadAvatar(user, user.userId!!)
} else {
binding!!.currentAccount.userIcon.visibility = View.INVISIBLE
}

View File

@ -3,6 +3,8 @@
*
* @author Mario Danic
* @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) 2017-2020 Mario Danic <mario@lovelyhq.com>
*
@ -22,7 +24,6 @@
package com.nextcloud.talk.utils;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
@ -33,11 +34,7 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.VectorDrawable;
import android.net.Uri;
import android.os.Build;
import android.text.Spannable;
@ -51,48 +48,26 @@ import android.text.style.AbsoluteSizeSpan;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import com.facebook.common.executors.UiThreadImmediateExecutorService;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.ControllerListener;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.common.RotationOptions;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor;
import com.facebook.imagepipeline.postprocessors.RoundPostprocessor;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.facebook.widget.text.span.BetterImageSpan;
import com.google.android.material.chip.ChipDrawable;
import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.events.UserMentionClickEvent;
import com.nextcloud.talk.extensions.ImageViewExtensionsKt;
import com.nextcloud.talk.ui.theme.ViewThemeUtils;
import com.nextcloud.talk.utils.text.Spans;
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.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -103,12 +78,16 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.XmlRes;
import androidx.appcompat.widget.AppCompatDrawableManager;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.graphics.ColorUtils;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.emoji.text.EmojiCompat;
import coil.Coil;
import coil.request.ImageRequest;
import coil.target.Target;
import coil.transform.CircleCropTransformation;
import third.parties.fresco.BetterImageSpan;
import static com.nextcloud.talk.utils.FileSortOrder.sort_a_to_z_id;
import static com.nextcloud.talk.utils.FileSortOrder.sort_big_to_small_id;
@ -119,8 +98,6 @@ import static com.nextcloud.talk.utils.FileSortOrder.sort_z_to_a_id;
public class DisplayUtils {
private static final String TAG = "DisplayUtils";
private static final int INDEX_LUMINATION = 2;
private static final double MAX_LIGHTNESS = 0.92;
@ -154,33 +131,6 @@ public class DisplayUtils {
textView.setMovementMethod(LinkMovementMethod.getInstance());
}
private static void updateViewSize(@Nullable ImageInfo imageInfo, SimpleDraweeView draweeView) {
if (imageInfo != null && draweeView.getId() != R.id.messageUserAvatar) {
int maxSize = draweeView.getContext().getResources().getDimensionPixelSize(R.dimen.maximum_file_preview_size);
draweeView.getLayoutParams().width = imageInfo.getWidth() > maxSize ? maxSize : imageInfo.getWidth();
draweeView.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
draweeView.setAspectRatio((float) imageInfo.getWidth() / imageInfo.getHeight());
draweeView.requestLayout();
}
}
public static Drawable getRoundedDrawable(Drawable drawable) {
Bitmap bitmap = getBitmap(drawable);
new RoundAsCirclePostprocessor(true).process(bitmap);
return new BitmapDrawable(bitmap);
}
public static Bitmap getRoundedBitmapFromVectorDrawableResource(Resources resources, int resource) {
VectorDrawable vectorDrawable = (VectorDrawable) ResourcesCompat.getDrawable(resources, resource, null);
Bitmap bitmap = getBitmap(vectorDrawable);
new RoundPostprocessor(true).process(bitmap);
return bitmap;
}
public static Drawable getRoundedBitmapDrawableFromVectorDrawableResource(Resources resources, int resource) {
return new BitmapDrawable(getRoundedBitmapFromVectorDrawableResource(resources, resource));
}
public static Bitmap getBitmap(Drawable drawable) {
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(),
@ -191,60 +141,6 @@ public class DisplayUtils {
return bitmap;
}
public static ImageRequest getImageRequestForUrl(String url) {
return getImageRequestForUrl(url, (User) null);
}
public static ImageRequest getImageRequestForUrl(String url, @Nullable User user) {
Map<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) {
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
context.getResources().getDisplayMetrics()) + 0.5f);
@ -254,31 +150,6 @@ public class DisplayUtils {
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) {
Drawable drawable = ResourcesCompat.getDrawable(res, drawableResId, null);
@ -305,10 +176,8 @@ public class DisplayUtils {
viewThemeUtils.material.colorChipDrawable(context, chip);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Configuration config = context.getResources().getConfiguration();
chip.setLayoutDirection(config.getLayoutDirection());
}
Configuration config = context.getResources().getConfiguration();
chip.setLayoutDirection(config.getLayoutDirection());
int drawable;
@ -335,33 +204,37 @@ public class DisplayUtils {
conversationUser.getBaseUrl(),
String.valueOf(label), true);
}
ImageRequest imageRequest = getImageRequestForUrl(url);
ImagePipeline imagePipeline = Fresco.getImagePipeline();
DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(
imageRequest,
context);
dataSource.subscribe(
new BaseBitmapDataSubscriber() {
ImageRequest imageRequest = new ImageRequest.Builder(context)
.data(url)
.crossfade(true)
.transformations(new CircleCropTransformation())
.target(new Target() {
@Override
protected void onNewResultImpl(Bitmap bitmap) {
if (bitmap != null) {
chip.setChipIcon(getRoundedDrawable(new BitmapDrawable(bitmap)));
public void onStart(@Nullable Drawable drawable) {
// A hack to refresh the chip icon
if (emojiEditText != null) {
emojiEditText.post(() -> emojiEditText.setTextKeepState(
emojiEditText.getText(),
TextView.BufferType.SPANNABLE));
}
}
@Override
public void onError(@Nullable Drawable drawable) {
}
@Override
public void onSuccess(@NonNull Drawable drawable) {
chip.setChipIcon(drawable);
// A hack to refresh the chip icon
if (emojiEditText != null) {
emojiEditText.post(() -> emojiEditText.setTextKeepState(
emojiEditText.getText(),
TextView.BufferType.SPANNABLE));
}
}
})
.build();
@Override
protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
}
},
UiThreadImmediateExecutorService.getInstance());
Coil.imageLoader(context).enqueue(imageRequest);
}
return chip;
@ -481,24 +354,23 @@ public class DisplayUtils {
Window window = activity.getWindow();
boolean isLightTheme = lightTheme(color);
if (window != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
View decor = window.getDecorView();
if (isLightTheme) {
int systemUiFlagLightStatusBar;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
systemUiFlagLightStatusBar = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR |
View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
} else {
systemUiFlagLightStatusBar = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
}
decor.setSystemUiVisibility(systemUiFlagLightStatusBar);
View decor = window.getDecorView();
if (isLightTheme) {
int systemUiFlagLightStatusBar;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
systemUiFlagLightStatusBar = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR |
View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
} else {
decor.setSystemUiVisibility(0);
systemUiFlagLightStatusBar = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
}
window.setStatusBarColor(color);
} else if (isLightTheme) {
window.setStatusBarColor(Color.BLACK);
decor.setSystemUiVisibility(systemUiFlagLightStatusBar);
} else {
decor.setSystemUiVisibility(0);
}
window.setStatusBarColor(color);
} else if (isLightTheme) {
window.setStatusBarColor(Color.BLACK);
}
}
@ -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;
if (!TextUtils.isEmpty(user.getUserId())) {
avatarId = user.getUserId();
@ -583,50 +455,13 @@ public class DisplayUtils {
avatarId = user.getUsername();
}
String avatarString = ApiUtils.getUrlForAvatar(user.getBaseUrl(), avatarId, true);
// clear cache
if (deleteCache) {
Uri avatarUri = Uri.parse(avatarString);
ImagePipeline imagePipeline = Fresco.getImagePipeline();
imagePipeline.evictFromMemoryCache(avatarUri);
imagePipeline.evictFromDiskCache(avatarUri);
imagePipeline.evictFromCache(avatarUri);
}
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
.setOldController(avatarImageView.getController())
.setAutoPlayAnimations(true)
.setImageRequest(DisplayUtils.getImageRequestForUrl(avatarString))
.build();
avatarImageView.setController(draweeController);
}
public static void loadAvatarPlaceholder(final SimpleDraweeView targetView) {
final Context context = targetView.getContext();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Drawable[] layers = new Drawable[2];
layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background);
layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground);
LayerDrawable layerDrawable = new LayerDrawable(layers);
targetView.getHierarchy().setPlaceholderImage(
DisplayUtils.getRoundedDrawable(layerDrawable));
ImageViewExtensionsKt.replaceAvatar(avatarImageView, user, avatarId, true);
} else {
targetView.getHierarchy().setPlaceholderImage(R.mipmap.ic_launcher);
ImageViewExtensionsKt.loadAvatar(avatarImageView, user, avatarId, true);
}
}
public static void loadImage(final SimpleDraweeView targetView, final ImageRequest imageRequest) {
final DraweeController newController = Fresco.newDraweeControllerBuilder()
.setOldController(targetView.getController())
.setAutoPlayAnimations(true)
.setImageRequest(imageRequest)
.build();
targetView.setController(newController);
}
public static @StringRes
int getSortOrderStringId(FileSortOrder sortOrder) {
switch (sortOrder.getName()) {
@ -649,7 +484,7 @@ public class DisplayUtils {
/**
* calculates the relative time string based on the given modification timestamp.
*
* @param context the app's context
* @param context the app's context
* @param modificationTimestamp the UNIX timestamp of the file modification time in milliseconds.
* @return a relative time string
*/

View File

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

View File

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

View File

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

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

View File

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

View File

@ -8,12 +8,9 @@
package com.nextcloud.talk.utils.ssl
import android.os.Build
import java.io.IOException
import java.net.InetAddress
import java.net.Socket
import java.security.GeneralSecurityException
import java.util.LinkedList
import javax.net.ssl.KeyManager
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocket
@ -35,69 +32,12 @@ class SSLSocketFactoryCompat(
var cipherSuites: Array<String>? = null
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Since Android 6.0 (API level 23),
// - TLSv1.1 and TLSv1.2 is enabled by default
// - SSLv3 is disabled by default
// - all modern ciphers are activated by default
protocols = 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
}
}
// Since Android 6.0 (API level 23),
// - TLSv1.1 and TLSv1.2 is enabled by default
// - SSLv3 is disabled by default
// - all modern ciphers are activated by default
protocols = null
cipherSuites = null
}
}

View File

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

View File

@ -41,7 +41,6 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import android.util.Log;
import com.nextcloud.talk.events.PeerConnectionEvent;
@ -388,22 +387,18 @@ public class WebRtcAudioManager {
*/
@Deprecated
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);
for (AudioDeviceInfo device : devices) {
final int type = device.getType();
if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
Log.d(TAG, "hasWiredHeadset: found wired headset");
return true;
} else if (type == AudioDeviceInfo.TYPE_USB_DEVICE) {
Log.d(TAG, "hasWiredHeadset: found USB audio device");
return true;
}
@SuppressLint("WrongConstant") final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
for (AudioDeviceInfo device : devices) {
final int type = device.getType();
if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
Log.d(TAG, "hasWiredHeadset: found wired headset");
return true;
} else if (type == AudioDeviceInfo.TYPE_USB_DEVICE) {
Log.d(TAG, "hasWiredHeadset: found USB audio device");
return true;
}
return false;
}
return false;
}
public void updateAudioDeviceState() {

View File

@ -22,7 +22,7 @@
*
*/
package third_parties.daveKoeller;
package third.parties.daveKoeller;
import java.io.Serializable;
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"
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_height="72dp"
@ -44,7 +43,7 @@
android:layout_alignParentStart="true"
android:layout_centerVertical="true">
<com.facebook.drawee.view.SimpleDraweeView
<ImageView
android:id="@+id/user_icon"
android:layout_width="@dimen/small_item_height"
android:layout_height="@dimen/small_item_height"
@ -54,10 +53,7 @@
android:layout_marginEnd="1dp"
android:layout_marginBottom="1dp"
android:contentDescription="@string/avatar"
android:src="@drawable/account_circle_48dp"
fresco:placeholderImage="@drawable/account_circle_48dp"
fresco:failureImage="@drawable/account_circle_48dp"
app:roundAsCircle="true"/>
android:src="@drawable/account_circle_48dp" />
</FrameLayout>

View File

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

View File

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

View File

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

View File

@ -3,6 +3,8 @@
~
~ @author Mario Danic
~ @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) 2017-2018 Mario Danic <mario@lovelyhq.com>
~
@ -36,36 +38,37 @@
android:animateLayoutChanges="true"
android:orientation="horizontal">
<com.facebook.drawee.view.SimpleDraweeView
<ImageButton
android:id="@+id/callAnswerVoiceOnlyView"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="24dp"
android:visibility="gone"
app:backgroundImage="@color/nc_darkGreen"
app:placeholderImage="@drawable/ic_call_white_24dp"
app:roundAsCircle="true"
tools:visibility="visible" />
android:background="@drawable/shape_oval"
android:backgroundTint="@color/nc_darkGreen"
android:src="@drawable/ic_call_white_24dp"
android:contentDescription="@string/nc_call_button_content_description_answer_voice_only" />
<com.facebook.drawee.view.SimpleDraweeView
<ImageButton
android:id="@+id/hangupButton"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="24dp"
app:backgroundImage="@color/nc_darkRed"
app:placeholderImage="@drawable/ic_call_end_white_24px"
app:roundAsCircle="true" />
android:background="@drawable/shape_oval"
android:backgroundTint="@color/nc_darkRed"
android:src="@drawable/ic_call_end_white_24px"
android:contentDescription="@string/nc_call_button_content_description_hangup" />
<com.facebook.drawee.view.SimpleDraweeView
<ImageButton
android:id="@+id/callAnswerCameraView"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="24dp"
android:background="@drawable/shape_oval"
android:backgroundTint="@color/nc_darkGreen"
android:src="@drawable/ic_videocam_white_24px"
android:visibility="gone"
app:backgroundImage="@color/nc_darkGreen"
app:placeholderImage="@drawable/ic_videocam_white_24px"
app:roundAsCircle="true"
tools:visibility="visible" />
tools:visibility="visible"
android:contentDescription="@string/nc_call_button_content_description_answer_video_call" />
</LinearLayout>
<RelativeLayout
@ -111,11 +114,11 @@
</RelativeLayout>
<com.facebook.drawee.view.SimpleDraweeView
<ImageView
android:id="@+id/avatarImageView"
android:layout_width="@dimen/avatar_size_big"
android:layout_height="@dimen/avatar_size_big"
android:layout_centerInParent="true"
app:roundAsCircle="true" />
android:contentDescription="@string/avatar" />
</RelativeLayout>

View File

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

View File

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

View File

@ -25,7 +25,6 @@
<com.yarolegovich.mp.MaterialPreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:apc="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"
tools:background="@color/white"
android:id="@+id/settings_screen"
@ -119,16 +118,14 @@
</com.google.android.material.card.MaterialCardView>
<com.facebook.drawee.view.SimpleDraweeView
<ImageView
android:id="@+id/avatar_image"
android:layout_width="@dimen/avatar_size_big"
android:layout_height="@dimen/avatar_size_big"
android:layout_centerHorizontal="true"
android:src="@drawable/account_circle_96dp"
android:transitionName="userAvatar.transitionTag"
apc:roundAsCircle="true"
fresco:failureImage="@drawable/account_circle_96dp"
fresco:placeholderImage="@drawable/account_circle_96dp" />
android:contentDescription="@string/avatar" />
<com.yarolegovich.mp.MaterialStandardPreference
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"
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_height="72dp"
@ -45,7 +44,7 @@
android:layout_alignParentStart="true"
android:layout_centerVertical="true">
<com.facebook.drawee.view.SimpleDraweeView
<ImageView
android:id="@+id/user_icon"
android:layout_width="@dimen/small_item_height"
android:layout_height="@dimen/small_item_height"
@ -55,10 +54,7 @@
android:layout_marginEnd="1dp"
android:layout_marginBottom="1dp"
android:contentDescription="@string/avatar"
android:src="@drawable/account_circle_48dp"
fresco:placeholderImage="@drawable/account_circle_48dp"
fresco:failureImage="@drawable/account_circle_48dp"
app:roundAsCircle="true"/>
android:src="@drawable/account_circle_48dp" />
<ImageView
android:id="@+id/ticker"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,22 +17,21 @@
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
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:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="8dp"
android:layout_gravity="center"
app:roundAsCircle="true" />
android:contentDescription="@string/avatar"/>
<TextView
android:id="@+id/poll_voter_name"

View File

@ -19,19 +19,18 @@
-->
<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"
android:layout_width="match_parent"
android:layout_height="@dimen/item_height"
android:background="?android:attr/selectableItemBackground">
<com.facebook.drawee.view.SimpleDraweeView
<ImageView
android:id="@+id/avatar"
android:layout_width="@dimen/avatar_size"
android:layout_height="@dimen/avatar_size"
android:layout_gravity="center_vertical"
android:layout_margin="@dimen/standard_margin"
app:roundAsCircle="true" />
android:contentDescription="@string/avatar"/>
<TextView
android:id="@+id/name"

View File

@ -19,7 +19,6 @@
-->
<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"
android:id="@+id/referenceWrapper"
android:layout_width="match_parent"
@ -32,64 +31,64 @@
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:background="@color/low_emphasis_text"
tools:layout_height="100dp"/>
tools:layout_height="100dp" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/referenceName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:ellipsize="end"
android:lineSpacingMultiplier="1.2"
android:maxLines="2"
android:textAlignment="viewStart"
android:textIsSelectable="false"
android:layout_marginStart="10dp"
android:visibility="gone"
android:ellipsize="end"
android:maxLines="2"
android:textStyle="bold"
android:visibility="gone"
tools:text="Name of Website"
tools:visibility="visible"/>
tools:visibility="visible" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/referenceDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/referenceName"
android:layout_marginStart="10dp"
android:ellipsize="end"
android:lineSpacingMultiplier="1.2"
android:maxLines="2"
android:textAlignment="viewStart"
android:textIsSelectable="false"
android:layout_marginStart="10dp"
android:visibility="gone"
android:ellipsize="end"
android:maxLines="2"
tools:text="Description of Website"
tools:visibility="visible"/>
tools:visibility="visible" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/referenceLink"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/referenceDescription"
android:lineSpacingMultiplier="1.2"
android:textAlignment="viewStart"
android:textIsSelectable="false"
android:layout_marginStart="10dp"
android:textColor="@color/medium_emphasis_text"
android:singleLine="true"
android:lines="1"
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"
tools:text="http://nextcloud.com"
tools:visibility="visible"/>
tools:visibility="visible" />
<com.facebook.drawee.view.SimpleDraweeView
<ImageView
android:id="@+id/referenceThumbImage"
android:layout_width="match_parent"
android:layout_height="120dp"
android:scaleType="fitEnd"
android:layout_below="@id/referenceLink"
android:layout_marginTop="5dp"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:scaleType="fitEnd"
android:visibility="gone"
app:roundedCornerRadius="6dp"
tools:visibility="visible"/>
tools:visibility="visible"
tools:ignore="ContentDescription" />
</RelativeLayout>

View File

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

View File

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

View File

@ -29,7 +29,7 @@
android:orientation="vertical">
<com.elyeproj.loaderviewlibrary.LoaderTextView
android:id="@+id/simple_drawee_view"
android:id="@+id/loader_text_view"
android:layout_width="@dimen/avatar_size"
android:layout_height="@dimen/avatar_size"
android:layout_centerVertical="true"
@ -42,7 +42,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
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" />
</RelativeLayout>

View File

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

View File

@ -3,6 +3,8 @@
~
~ @author Mario Danic
~ @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) 2017-2018 Mario Danic <mario@lovelyhq.com>
~
@ -38,12 +40,11 @@
android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/double_margin_between_elements">
<com.facebook.drawee.view.SimpleDraweeView
<ImageView
android:id="@id/dialogAvatar"
android:layout_width="@dimen/small_item_height"
android:layout_height="@dimen/small_item_height"
android:contentDescription="@null"
app:roundAsCircle="true" />
android:contentDescription="@null" />
<ImageView
android:id="@+id/favoriteConversationImageView"

View File

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

View File

@ -3,6 +3,8 @@
~
~ @author Mario Danic
~ @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) 2017-2018 Mario Danic <mario@lovelyhq.com>
~
@ -35,14 +37,13 @@
android:layout_margin="@dimen/double_margin_between_elements"
tools:background="@color/white">
<com.facebook.drawee.view.SimpleDraweeView
<ImageView
android:id="@+id/thumbnail"
android:layout_width="@dimen/small_item_height"
android:layout_height="@dimen/small_item_height"
android:importantForAccessibility="no"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundAsCircle="true" />
app:layout_constraintTop_toTopOf="parent" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/conversation_title"

View File

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

View File

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

View File

@ -75,7 +75,5 @@
<color name="grey_200">#818181</color>
<color name="dialog_background">#353535</color>
<color name="vote_dialog_background">#424242</color>
</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
possible?! don't use this to set the background of dialogs -->
<color name="dialog_background">#FFFFFF</color>
<color name="vote_dialog_background">#FFFFFF</color>
</resources>

View File

@ -2,6 +2,8 @@
~ Nextcloud Talk application
~
~ @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>
~
~ 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_grid_item_min_height">180dp</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_self_participant_progress_bar_size">48dp</dimen>
<dimen name="zero">0dp</dimen>

View File

@ -4,6 +4,8 @@
~ @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>
@ -211,6 +213,14 @@
<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_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 -->
<string name="nc_pip_microphone_mute">Mute microphone</string>

View File

@ -1,2 +1,2 @@
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>