mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-20 03:59:35 +01:00
New work
Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
parent
e11705f230
commit
059aaedca8
@ -30,6 +30,12 @@ if (taskRequest.contains("Gplay") || taskRequest.contains("findbugs") ||
|
||||
apply from: 'gplay.gradle'
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '29.0.2'
|
||||
@ -148,7 +154,7 @@ android {
|
||||
ext {
|
||||
work_version = "2.3.0-alpha02"
|
||||
koin_version = "2.1.0-alpha-1"
|
||||
lifecycle_version = "2.2.0-beta01"
|
||||
lifecycle_version = "2.2.0-rc01"
|
||||
coil_version = "0.8.0"
|
||||
room_version = "2.2.0"
|
||||
}
|
||||
@ -218,8 +224,8 @@ dependencies {
|
||||
androidTestImplementation "androidx.work:work-testing:$work_version"
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.1.0-beta01'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
|
||||
implementation 'com.google.android.material:material:1.2.0-alpha01'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta3'
|
||||
implementation 'com.github.vanniktech:Emoji:0.6.0'
|
||||
implementation group: 'androidx.emoji', name: 'emoji-bundled', version: '1.0.0'
|
||||
implementation 'org.michaelevans.colorart:library:0.0.3'
|
||||
@ -229,7 +235,7 @@ dependencies {
|
||||
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
|
||||
|
||||
implementation 'androidx.biometric:biometric:1.0.0-rc01'
|
||||
implementation 'androidx.biometric:biometric:1.0.0-rc02'
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
|
||||
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
@ -244,9 +250,9 @@ dependencies {
|
||||
implementation 'com.bluelinelabs:conductor-autodispose:3.0.0-rc2'
|
||||
implementation "com.github.miquelbeltran:conductor-viewmodel:1.0.3"
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.14.4'
|
||||
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.4'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:3.14.4'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.2.2'
|
||||
implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.2.2'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
|
||||
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
|
||||
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.6.2'
|
||||
@ -327,8 +333,8 @@ dependencies {
|
||||
androidTestImplementation('androidx.test.espresso:espresso-core:3.3.0-alpha02', {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
})
|
||||
findbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.9.0'
|
||||
findbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.4.6'
|
||||
findbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.10.0'
|
||||
findbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.4.7'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
|
||||
implementation 'com.github.Kennyc1012:BottomSheet:2.4.1'
|
||||
}
|
||||
}
|
@ -48,7 +48,7 @@ public class MenuItem extends AbstractFlexibleItem<MenuItem.MenuItemViewHolder>
|
||||
this.title = title;
|
||||
this.tag = tag;
|
||||
this.icon = icon;
|
||||
padding = (int) DisplayUtils.convertDpToPixel(16,
|
||||
padding = (int) DisplayUtils.INSTANCE.convertDpToPixel(16,
|
||||
NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext());
|
||||
}
|
||||
|
||||
|
@ -98,7 +98,7 @@ public class MagicOutcomingTextMessageViewHolder
|
||||
if (individualHashMap.get("type").equals("user") || individualHashMap.get("type")
|
||||
.equals("guest") || individualHashMap.get("type").equals("call")) {
|
||||
messageString =
|
||||
DisplayUtils.searchAndReplaceWithMentionSpan(messageText.getContext(),
|
||||
DisplayUtils.INSTANCE.searchAndReplaceWithMentionSpan(messageText.getContext(),
|
||||
messageString,
|
||||
individualHashMap.get("id"),
|
||||
individualHashMap.get("name"),
|
||||
@ -124,14 +124,14 @@ public class MagicOutcomingTextMessageViewHolder
|
||||
Resources resources = NextcloudTalkApplication.Companion.getSharedApplication().getResources();
|
||||
if (message.isGrouped) {
|
||||
Drawable bubbleDrawable =
|
||||
DisplayUtils.getMessageSelector(
|
||||
DisplayUtils.INSTANCE.getMessageSelector(
|
||||
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
||||
resources.getColor(R.color.transparent),
|
||||
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
||||
R.drawable.shape_grouped_outcoming_message);
|
||||
ViewCompat.setBackground(bubble, bubbleDrawable);
|
||||
} else {
|
||||
Drawable bubbleDrawable = DisplayUtils.getMessageSelector(
|
||||
Drawable bubbleDrawable = DisplayUtils.INSTANCE.getMessageSelector(
|
||||
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
||||
resources.getColor(R.color.transparent),
|
||||
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
||||
|
@ -96,7 +96,7 @@ public class MagicPreviewMessageViewHolder
|
||||
LayerDrawable layerDrawable = new LayerDrawable(layers);
|
||||
|
||||
userAvatar.getHierarchy()
|
||||
.setPlaceholderImage(DisplayUtils.getRoundedDrawable(layerDrawable));
|
||||
.setPlaceholderImage(DisplayUtils.INSTANCE.getRoundedDrawable(layerDrawable));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,7 +104,7 @@ public class MagicPreviewMessageViewHolder
|
||||
if (message.getMessageType() == ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE) {
|
||||
// it's a preview for a Nextcloud share
|
||||
messageText.setText(message.getSelectedIndividualHashMap().get("name"));
|
||||
DisplayUtils.setClickableString(message.getSelectedIndividualHashMap().get("name"),
|
||||
DisplayUtils.INSTANCE.setClickableString(message.getSelectedIndividualHashMap().get("name"),
|
||||
message.getSelectedIndividualHashMap().get("link"), messageText);
|
||||
if (message.getSelectedIndividualHashMap().containsKey("mimetype")) {
|
||||
image.getHierarchy()
|
||||
@ -145,10 +145,10 @@ public class MagicPreviewMessageViewHolder
|
||||
});
|
||||
} else if (message.getMessageType() == ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE) {
|
||||
messageText.setText("GIPHY");
|
||||
DisplayUtils.setClickableString("GIPHY", "https://giphy.com", messageText);
|
||||
DisplayUtils.INSTANCE.setClickableString("GIPHY", "https://giphy.com", messageText);
|
||||
} else if (message.getMessageType() == ChatMessage.MessageType.SINGLE_LINK_TENOR_MESSAGE) {
|
||||
messageText.setText("Tenor");
|
||||
DisplayUtils.setClickableString("Tenor", "https://tenor.com", messageText);
|
||||
DisplayUtils.INSTANCE.setClickableString("Tenor", "https://tenor.com", messageText);
|
||||
} else {
|
||||
if (message.getMessageType().equals(ChatMessage.MessageType.SINGLE_LINK_IMAGE_MESSAGE)) {
|
||||
image.setOnClickListener(v -> {
|
||||
|
@ -66,7 +66,7 @@ public class MagicSystemMessageViewHolder
|
||||
pressedColor = normalColor;
|
||||
mentionColor = resources.getColor(R.color.nc_author_text);
|
||||
|
||||
Drawable bubbleDrawable = DisplayUtils.getMessageSelector(normalColor,
|
||||
Drawable bubbleDrawable = DisplayUtils.INSTANCE.getMessageSelector(normalColor,
|
||||
resources.getColor(R.color.transparent), pressedColor,
|
||||
R.drawable.shape_grouped_incoming_message);
|
||||
ViewCompat.setBackground(bubble, bubbleDrawable);
|
||||
@ -80,7 +80,7 @@ public class MagicSystemMessageViewHolder
|
||||
|| individualHashMap.get("type").equals("guest")
|
||||
|| individualHashMap.get("type").equals("call"))) {
|
||||
messageString =
|
||||
DisplayUtils.searchAndColor(messageString, "@" + individualHashMap.get("name"),
|
||||
DisplayUtils.INSTANCE.searchAndColor(messageString, "@" + individualHashMap.get("name"),
|
||||
mentionColor);
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ public class MentionAutocompleteCallback implements AutocompleteCallback<Mention
|
||||
|
||||
editable.replace(start, end, replacementStringBuilder.toString() + " ");
|
||||
Spans.MentionChipSpan mentionChipSpan =
|
||||
new Spans.MentionChipSpan(DisplayUtils.getDrawableForMentionChipSpan(context,
|
||||
new Spans.MentionChipSpan(DisplayUtils.INSTANCE.getDrawableForMentionChipSpan(context,
|
||||
item.getId(), item.getLabel(), conversationUser, item.getSource(),
|
||||
R.xml.chip_you, editText),
|
||||
BetterImageSpan.ALIGN_CENTER,
|
||||
|
@ -1988,7 +1988,7 @@ public class CallController extends BaseController {
|
||||
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
|
||||
.setOldController(avatarImageView.getController())
|
||||
.setImageRequest(
|
||||
DisplayUtils.getImageRequestForUrl(ApiUtils.getUrlForAvatarWithName(baseUrl,
|
||||
DisplayUtils.INSTANCE.getImageRequestForUrl(ApiUtils.getUrlForAvatarWithName(baseUrl,
|
||||
userId,
|
||||
R.dimen.avatar_size_big), null))
|
||||
.build();
|
||||
|
@ -415,7 +415,7 @@ public class CallNotificationController extends BaseController {
|
||||
avatarImageView.setVisibility(View.VISIBLE);
|
||||
|
||||
ImageRequest imageRequest =
|
||||
DisplayUtils.getImageRequestForUrl(
|
||||
DisplayUtils.INSTANCE.getImageRequestForUrl(
|
||||
ApiUtils.getUrlForAvatarWithName(userBeingCalled.getBaseUrl(),
|
||||
currentConversation.getName(), R.dimen.avatar_size_very_big), null);
|
||||
|
||||
@ -465,12 +465,12 @@ public class CallNotificationController extends BaseController {
|
||||
break;
|
||||
case GROUP_CONVERSATION:
|
||||
avatarImageView.getHierarchy()
|
||||
.setImage(DisplayUtils.getRoundedDrawable(
|
||||
.setImage(DisplayUtils.INSTANCE.getRoundedDrawable(
|
||||
context.getDrawable(R.drawable.ic_people_group_white_24px))
|
||||
, 100, true);
|
||||
case PUBLIC_CONVERSATION:
|
||||
avatarImageView.getHierarchy()
|
||||
.setImage(DisplayUtils.getRoundedDrawable(
|
||||
.setImage(DisplayUtils.INSTANCE.getRoundedDrawable(
|
||||
context.getDrawable(R.drawable.ic_people_group_white_24px))
|
||||
, 100, true);
|
||||
break;
|
||||
|
@ -316,7 +316,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
|
||||
) {
|
||||
val avatarSize = DisplayUtils.convertDpToPixel(
|
||||
conversationVoiceCallMenuItem?.icon!!
|
||||
.intrinsicWidth.toFloat(), activity
|
||||
.intrinsicWidth.toFloat(), activity!!
|
||||
)
|
||||
.toInt()
|
||||
|
||||
@ -384,13 +384,13 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
|
||||
|
||||
adapter = MessagesListAdapter(
|
||||
conversationUser?.userId, messageHolders, ImageLoader { imageView, url, payload ->
|
||||
val draweeController = Fresco.newDraweeControllerBuilder()
|
||||
/*val draweeController = Fresco.newDraweeControllerBuilder()
|
||||
.setImageRequest(DisplayUtils.getImageRequestForUrl(url, conversationUser))
|
||||
.setControllerListener(DisplayUtils.getImageControllerListener(imageView))
|
||||
.setOldController(imageView.controller)
|
||||
.setAutoPlayAnimations(true)
|
||||
.build()
|
||||
imageView.controller = draweeController
|
||||
imageView.controller = draweeController*/
|
||||
})
|
||||
} else {
|
||||
messagesListView?.visibility = View.VISIBLE
|
||||
@ -1136,9 +1136,8 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
|
||||
isFromTheFuture: Boolean,
|
||||
timeout: Int
|
||||
) {
|
||||
val xChatLastGivenHeader: String? = response.headers()
|
||||
.get("X-Chat-Last-Given")
|
||||
if (response.headers().size() > 0 && !TextUtils.isEmpty(xChatLastGivenHeader)) {
|
||||
val xChatLastGivenHeader: String? = response.headers().get("X-Chat-Last-Given")
|
||||
if (response.headers().size > 0 && !TextUtils.isEmpty(xChatLastGivenHeader)) {
|
||||
|
||||
val header = Integer.parseInt(xChatLastGivenHeader!!)
|
||||
if (header > 0) {
|
||||
|
@ -417,10 +417,10 @@ public class OperationsMenuController extends BaseController {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
|
||||
if (everythingOK) {
|
||||
resultImageView.setImageDrawable(DisplayUtils.getTintedDrawable(getResources(), R.drawable
|
||||
resultImageView.setImageDrawable(DisplayUtils.INSTANCE.getTintedDrawable(getResources(), R.drawable
|
||||
.ic_check_circle_black_24dp, R.color.nc_darkGreen));
|
||||
} else {
|
||||
resultImageView.setImageDrawable(DisplayUtils.getTintedDrawable(getResources(), R.drawable
|
||||
resultImageView.setImageDrawable(DisplayUtils.INSTANCE.getTintedDrawable(getResources(), R.drawable
|
||||
.ic_cancel_black_24dp, R.color.nc_darkRed));
|
||||
}
|
||||
|
||||
@ -448,7 +448,7 @@ public class OperationsMenuController extends BaseController {
|
||||
if (everythingOK) {
|
||||
eventBus.post(new BottomSheetLockEvent(true, 2500, true, true));
|
||||
} else {
|
||||
resultImageView.setImageDrawable(DisplayUtils.getTintedDrawable(getResources(), R.drawable
|
||||
resultImageView.setImageDrawable(DisplayUtils.INSTANCE.getTintedDrawable(getResources(), R.drawable
|
||||
.ic_cancel_black_24dp, R.color.nc_darkRed));
|
||||
okButton.setOnClickListener(
|
||||
v -> eventBus.post(new BottomSheetLockEvent(true, 0, operationCode != 99
|
||||
|
@ -25,4 +25,8 @@ import lombok.Data;
|
||||
@Data
|
||||
public class UserMentionClickEvent {
|
||||
public final String userId;
|
||||
|
||||
public UserMentionClickEvent(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
||||
|
@ -438,7 +438,7 @@ public class NotificationWorker extends Worker {
|
||||
}
|
||||
|
||||
ImageRequest imageRequest =
|
||||
DisplayUtils.getImageRequestForUrl(avatarUrl, null);
|
||||
DisplayUtils.INSTANCE.getImageRequestForUrl(avatarUrl, null);
|
||||
ImagePipeline imagePipeline = Fresco.getImagePipeline();
|
||||
DataSource<CloseableReference<CloseableImage>> dataSource =
|
||||
imagePipeline.fetchDecodedImage(imageRequest, context);
|
||||
|
@ -27,7 +27,7 @@ import org.parceler.Parcel;
|
||||
|
||||
@Parcel
|
||||
@Data
|
||||
@JsonObject()
|
||||
@JsonObject
|
||||
public class UserProfileData {
|
||||
@JsonField(name = "display-name")
|
||||
public String displayName;
|
||||
|
@ -117,7 +117,7 @@ fun createOkHttpClient(
|
||||
// Trust own CA and all self-signed certs
|
||||
httpClient.sslSocketFactory(sslSocketFactoryCompat, magicTrustManager)
|
||||
httpClient.retryOnConnectionFailure(true)
|
||||
httpClient.hostnameVerifier(magicTrustManager.getHostnameVerifier(OkHostnameVerifier.INSTANCE))
|
||||
httpClient.hostnameVerifier(magicTrustManager.getHostnameVerifier(OkHostnameVerifier))
|
||||
|
||||
httpClient.dispatcher(dispatcher)
|
||||
if (Proxy.NO_PROXY != proxy) {
|
||||
|
@ -122,7 +122,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
|
||||
?.let {
|
||||
DisplayUtils.convertDpToPixel(
|
||||
it,
|
||||
activity
|
||||
activity!!
|
||||
)
|
||||
.toInt()
|
||||
}
|
||||
@ -419,7 +419,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
|
||||
negativeButton(R.string.nc_cancel)
|
||||
icon(
|
||||
drawable = DisplayUtils.getTintedDrawable(
|
||||
resources, drawable
|
||||
resources!!, drawable
|
||||
.ic_delete_grey600_24dp, R.color.nc_darkRed
|
||||
)
|
||||
)
|
||||
|
@ -34,7 +34,7 @@ class Images {
|
||||
context: Context,
|
||||
url: String,
|
||||
userEntity:
|
||||
UserEntity,
|
||||
UserEntity?,
|
||||
target: Target?,
|
||||
lifecycleOwner: LifecycleOwner?,
|
||||
vararg transformations: Transformation
|
||||
@ -50,7 +50,7 @@ class Images {
|
||||
target(target)
|
||||
}
|
||||
|
||||
if (url.startsWith(userEntity.baseUrl) && url.contains(
|
||||
if (userEntity != null && url.startsWith(userEntity.baseUrl) && url.contains(
|
||||
"index.php/core/preview?fileId="
|
||||
)
|
||||
) {
|
||||
|
@ -45,14 +45,14 @@ class NetworkUtils {
|
||||
.header("User-Agent", ApiUtils.getUserAgent())
|
||||
.header("Accept", "application/json")
|
||||
.header("OCS-APIRequest", "true")
|
||||
.method(original.method(), original.body())
|
||||
.method(original.method, original.body)
|
||||
.build()
|
||||
|
||||
val response = chain.proceed(request)
|
||||
|
||||
if (request.url().encodedPath().contains("/avatar/")) {
|
||||
if (request.url.encodedPath.contains("/avatar/")) {
|
||||
AvatarStatusCodeHolder.getInstance()
|
||||
.statusCode = response.code()
|
||||
.statusCode = response.code
|
||||
}
|
||||
|
||||
return response
|
||||
@ -68,7 +68,7 @@ class NetworkUtils {
|
||||
route: Route?,
|
||||
response: Response
|
||||
): Request? {
|
||||
if (response.request().header(authenticatorType) != null) {
|
||||
if (response.request.header(authenticatorType) != null) {
|
||||
return null
|
||||
}
|
||||
|
||||
@ -77,13 +77,13 @@ class NetworkUtils {
|
||||
var attemptsCount = 0
|
||||
|
||||
|
||||
while ({ countedResponse = countedResponse?.priorResponse(); countedResponse }() != null) {
|
||||
while ({ countedResponse = countedResponse?.priorResponse; countedResponse }() != null) {
|
||||
attemptsCount++
|
||||
if (attemptsCount == 3) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return response.request()
|
||||
return response.request
|
||||
.newBuilder()
|
||||
.header(authenticatorType, credentials)
|
||||
.build()
|
||||
|
@ -1,436 +0,0 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* Copyright (C) 2017 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 android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.VectorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
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.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.ColorRes;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.XmlRes;
|
||||
import androidx.appcompat.widget.AppCompatDrawableManager;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import androidx.emoji.text.EmojiCompat;
|
||||
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.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.events.UserMentionClickEvent;
|
||||
import com.nextcloud.talk.models.database.UserEntity;
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
||||
import com.nextcloud.talk.utils.text.Spans;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
public class DisplayUtils {
|
||||
|
||||
private static final String TAG = "DisplayUtils";
|
||||
|
||||
public static void setClickableString(String string, String url, TextView textView) {
|
||||
SpannableString spannableString = new SpannableString(string);
|
||||
spannableString.setSpan(new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(@Nonnull View widget) {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
NextcloudTalkApplication.Companion.getSharedApplication()
|
||||
.getApplicationContext()
|
||||
.startActivity(browserIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(@NonNull TextPaint ds) {
|
||||
super.updateDrawState(ds);
|
||||
ds.setUnderlineText(false);
|
||||
}
|
||||
}, 0, string.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
textView.setText(spannableString);
|
||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
|
||||
private static void updateViewSize(@Nullable ImageInfo imageInfo, SimpleDraweeView draweeView) {
|
||||
if (imageInfo != null) {
|
||||
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) resources.getDrawable(resource);
|
||||
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));
|
||||
}
|
||||
|
||||
private static Bitmap getBitmap(Drawable drawable) {
|
||||
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
|
||||
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
drawable.draw(canvas);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public static ImageRequest getImageRequestForUrl(String url, @Nullable UserEntity userEntity) {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
if (userEntity != null && url.startsWith(userEntity.getBaseUrl()) && url.contains(
|
||||
"index.php/core/preview?fileId=")) {
|
||||
headers.put("Authorization",
|
||||
ApiUtils.getCredentials(userEntity.getUsername(), userEntity.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) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinalImageSet(String id, @javax.annotation.Nullable Object imageInfo,
|
||||
@javax.annotation.Nullable Animatable animatable) {
|
||||
updateViewSize((ImageInfo) imageInfo, draweeView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIntermediateImageSet(String id, @javax.annotation.Nullable Object imageInfo) {
|
||||
updateViewSize((ImageInfo) imageInfo, draweeView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIntermediateImageFailed(String id, Throwable throwable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(String id, Throwable throwable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelease(String id) {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static float convertDpToPixel(float dp, Context context) {
|
||||
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
|
||||
context.getResources().getDisplayMetrics()) + 0.5f);
|
||||
}
|
||||
|
||||
// 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 = res.getDrawable(drawableResId);
|
||||
Drawable mutableDrawable = drawable.mutate();
|
||||
int color = res.getColor(colorResId);
|
||||
mutableDrawable.setTint(color);
|
||||
return mutableDrawable;
|
||||
}
|
||||
|
||||
public static Drawable getDrawableForMentionChipSpan(Context context, String id,
|
||||
CharSequence label,
|
||||
UserEntity conversationUser, String type,
|
||||
@XmlRes int chipResource,
|
||||
@Nullable EditText emojiEditText) {
|
||||
ChipDrawable chip = ChipDrawable.createFromResource(context, chipResource);
|
||||
chip.setText(EmojiCompat.get().process(label));
|
||||
chip.setEllipsize(TextUtils.TruncateAt.MIDDLE);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Configuration config = context.getResources().getConfiguration();
|
||||
chip.setLayoutDirection(config.getLayoutDirection());
|
||||
}
|
||||
|
||||
int drawable;
|
||||
|
||||
boolean isCall = "call".equals(type) || "calls".equals(type);
|
||||
|
||||
if (!isCall) {
|
||||
if (chipResource == R.xml.chip_you) {
|
||||
drawable = R.drawable.mention_chip;
|
||||
} else {
|
||||
drawable = R.drawable.accent_circle;
|
||||
}
|
||||
|
||||
chip.setChipIcon(context.getDrawable(drawable));
|
||||
} else {
|
||||
chip.setChipIcon(
|
||||
getRoundedDrawable(context.getDrawable(R.drawable.ic_people_group_white_24px)));
|
||||
}
|
||||
|
||||
chip.setBounds(0, 0, chip.getIntrinsicWidth(), chip.getIntrinsicHeight());
|
||||
|
||||
if (!isCall) {
|
||||
String url = ApiUtils.getUrlForAvatarWithName(conversationUser.getBaseUrl(), id,
|
||||
R.dimen.avatar_size_big);
|
||||
if ("guests".equals(type) || "guest".equals(type)) {
|
||||
url = ApiUtils.getUrlForAvatarWithNameForGuests(conversationUser.getBaseUrl(),
|
||||
String.valueOf(label), R.dimen.avatar_size_big);
|
||||
}
|
||||
|
||||
ImageRequest imageRequest = getImageRequestForUrl(url, null);
|
||||
ImagePipeline imagePipeline = Fresco.getImagePipeline();
|
||||
DataSource<CloseableReference<CloseableImage>> dataSource =
|
||||
imagePipeline.fetchDecodedImage(imageRequest, context);
|
||||
|
||||
dataSource.subscribe(
|
||||
new BaseBitmapDataSubscriber() {
|
||||
@Override
|
||||
protected void onNewResultImpl(Bitmap bitmap) {
|
||||
if (bitmap != null) {
|
||||
chip.setChipIcon(getRoundedDrawable(new BitmapDrawable(bitmap)));
|
||||
|
||||
// A hack to refresh the chip icon
|
||||
if (emojiEditText != null) {
|
||||
emojiEditText.post(() -> emojiEditText.setTextKeepState(emojiEditText.getText(),
|
||||
TextView.BufferType.SPANNABLE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFailureImpl(
|
||||
DataSource<CloseableReference<CloseableImage>> dataSource) {
|
||||
}
|
||||
},
|
||||
UiThreadImmediateExecutorService.getInstance());
|
||||
}
|
||||
|
||||
return chip;
|
||||
}
|
||||
|
||||
public static Spannable searchAndReplaceWithMentionSpan(Context context, Spannable text,
|
||||
String id, String label, String type,
|
||||
UserEntity conversationUser,
|
||||
@XmlRes int chipXmlRes) {
|
||||
|
||||
Spannable spannableString = new SpannableString(text);
|
||||
String stringText = text.toString();
|
||||
|
||||
Matcher m = Pattern.compile("@" + label,
|
||||
Pattern.CASE_INSENSITIVE | Pattern.LITERAL | Pattern.MULTILINE)
|
||||
.matcher(spannableString);
|
||||
|
||||
ClickableSpan clickableSpan = new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(@NonNull View widget) {
|
||||
EventBus.getDefault().post(new UserMentionClickEvent(id));
|
||||
}
|
||||
};
|
||||
|
||||
int lastStartIndex = -1;
|
||||
Spans.MentionChipSpan mentionChipSpan;
|
||||
while (m.find()) {
|
||||
int start = stringText.indexOf(m.group(), lastStartIndex);
|
||||
int end = start + m.group().length();
|
||||
lastStartIndex = end;
|
||||
mentionChipSpan =
|
||||
new Spans.MentionChipSpan(DisplayUtils.getDrawableForMentionChipSpan(context,
|
||||
id, label, conversationUser, type, chipXmlRes, null),
|
||||
BetterImageSpan.ALIGN_CENTER, id,
|
||||
label);
|
||||
spannableString.setSpan(mentionChipSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
if ("user".equals(type) && !conversationUser.getUserId().equals(id)) {
|
||||
spannableString.setSpan(clickableSpan, start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
return spannableString;
|
||||
}
|
||||
|
||||
public static Spannable searchAndColor(Spannable text, String searchText, @ColorInt int color) {
|
||||
|
||||
Spannable spannableString = new SpannableString(text);
|
||||
String stringText = text.toString();
|
||||
if (TextUtils.isEmpty(text) || TextUtils.isEmpty(searchText)) {
|
||||
return spannableString;
|
||||
}
|
||||
|
||||
Matcher m = Pattern.compile(searchText,
|
||||
Pattern.CASE_INSENSITIVE | Pattern.LITERAL | Pattern.MULTILINE)
|
||||
.matcher(spannableString);
|
||||
|
||||
int textSize = NextcloudTalkApplication.Companion.getSharedApplication()
|
||||
.getResources()
|
||||
.getDimensionPixelSize(R.dimen
|
||||
.chat_text_size);
|
||||
|
||||
int lastStartIndex = -1;
|
||||
while (m.find()) {
|
||||
int start = stringText.indexOf(m.group(), lastStartIndex);
|
||||
int end = start + m.group().length();
|
||||
lastStartIndex = end;
|
||||
spannableString.setSpan(new ForegroundColorSpan(color), start, end,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
spannableString.setSpan(new StyleSpan(Typeface.BOLD), start, end,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
spannableString.setSpan(new AbsoluteSizeSpan(textSize), start, end,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
return spannableString;
|
||||
}
|
||||
|
||||
public static Drawable getMessageSelector(@ColorInt int normalColor, @ColorInt int selectedColor,
|
||||
@ColorInt int pressedColor, @DrawableRes int shape) {
|
||||
|
||||
Drawable vectorDrawable =
|
||||
ContextCompat.getDrawable(NextcloudTalkApplication.Companion.getSharedApplication()
|
||||
.getApplicationContext(),
|
||||
shape);
|
||||
Drawable drawable = DrawableCompat.wrap(vectorDrawable).mutate();
|
||||
DrawableCompat.setTintList(
|
||||
drawable,
|
||||
new ColorStateList(
|
||||
new int[][] {
|
||||
new int[] { android.R.attr.state_selected },
|
||||
new int[] { android.R.attr.state_pressed },
|
||||
new int[] { -android.R.attr.state_pressed, -android.R.attr.state_selected }
|
||||
},
|
||||
new int[] { selectedColor, pressedColor, normalColor }
|
||||
));
|
||||
return drawable;
|
||||
}
|
||||
|
||||
public static boolean isDarkModeActive(Context context, AppPreferences prefs) {
|
||||
if (prefs.getTheme().equals("night_yes")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int currentNightMode =
|
||||
context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||
switch (currentNightMode) {
|
||||
case Configuration.UI_MODE_NIGHT_NO:
|
||||
return false;
|
||||
case Configuration.UI_MODE_NIGHT_YES:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
413
app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.kt
Normal file
413
app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.kt
Normal file
@ -0,0 +1,413 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* Copyright (C) 2017 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 android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.VectorDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.Spanned
|
||||
import android.text.TextPaint
|
||||
import android.text.TextUtils
|
||||
import android.text.method.LinkMovementMethod
|
||||
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.widget.EditText
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.XmlRes
|
||||
import androidx.appcompat.widget.AppCompatDrawableManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.emoji.text.EmojiCompat
|
||||
import coil.Coil
|
||||
import coil.ImageLoader
|
||||
import coil.api.load
|
||||
import coil.target.Target
|
||||
import coil.transform.CircleCropTransformation
|
||||
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.view.SimpleDraweeView
|
||||
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.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.events.UserMentionClickEvent
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
import com.nextcloud.talk.newarch.utils.Images
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import com.nextcloud.talk.utils.text.Spans
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.util.regex.Pattern
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
object DisplayUtils {
|
||||
|
||||
private val TAG = "DisplayUtils"
|
||||
|
||||
fun setClickableString(
|
||||
string: String,
|
||||
url: String,
|
||||
textView: TextView
|
||||
) {
|
||||
val spannableString = SpannableString(string)
|
||||
spannableString.setSpan(object : ClickableSpan() {
|
||||
override fun onClick(widget: View) {
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
NextcloudTalkApplication.sharedApplication!!
|
||||
.applicationContext
|
||||
.startActivity(browserIntent)
|
||||
}
|
||||
|
||||
override fun updateDrawState(ds: TextPaint) {
|
||||
super.updateDrawState(ds)
|
||||
ds.isUnderlineText = false
|
||||
}
|
||||
}, 0, string.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
textView.text = spannableString
|
||||
textView.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
private fun updateViewSize(
|
||||
imageInfo: ImageInfo?,
|
||||
draweeView: SimpleDraweeView
|
||||
) {
|
||||
if (imageInfo != null) {
|
||||
val maxSize = draweeView.context
|
||||
.resources
|
||||
.getDimensionPixelSize(R.dimen.maximum_file_preview_size)
|
||||
draweeView.layoutParams.width = if (imageInfo.width > maxSize) maxSize else imageInfo.width
|
||||
draweeView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
draweeView.aspectRatio = imageInfo.width.toFloat() / imageInfo.height
|
||||
draweeView.requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
fun getRoundedDrawable(drawable: Drawable?): Drawable {
|
||||
val bitmap = getBitmap(drawable!!)
|
||||
RoundAsCirclePostprocessor(true).process(bitmap)
|
||||
return BitmapDrawable(bitmap)
|
||||
}
|
||||
|
||||
fun getRoundedBitmapFromVectorDrawableResource(
|
||||
resources: Resources,
|
||||
resource: Int
|
||||
): Bitmap {
|
||||
val vectorDrawable = resources.getDrawable(resource) as VectorDrawable
|
||||
val bitmap = getBitmap(vectorDrawable)
|
||||
RoundPostprocessor(true).process(bitmap)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
private fun getBitmap(drawable: Drawable): Bitmap {
|
||||
val bitmap = Bitmap.createBitmap(
|
||||
drawable.intrinsicWidth,
|
||||
drawable.intrinsicHeight, Bitmap.Config.ARGB_8888
|
||||
)
|
||||
val canvas = Canvas(bitmap)
|
||||
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||
drawable.draw(canvas)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
fun convertDpToPixel(
|
||||
dp: Float,
|
||||
context: Context
|
||||
): Float {
|
||||
return Math.round(
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, dp,
|
||||
context.resources.displayMetrics
|
||||
) + 0.5f
|
||||
)
|
||||
.toFloat()
|
||||
}
|
||||
|
||||
// Solution inspired by https://stackoverflow.com/questions/34936590/why-isnt-my-vector-drawable-scaling-as-expected
|
||||
fun useCompatVectorIfNeeded() {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
try {
|
||||
@SuppressLint("RestrictedApi") val drawableManager = AppCompatDrawableManager.get()
|
||||
val inflateDelegateClass =
|
||||
Class.forName("android.support.v7.widget.AppCompatDrawableManager\$InflateDelegate")
|
||||
val vdcInflateDelegateClass =
|
||||
Class.forName("android.support.v7.widget.AppCompatDrawableManager\$VdcInflateDelegate")
|
||||
|
||||
val constructor = vdcInflateDelegateClass.getDeclaredConstructor()
|
||||
constructor.isAccessible = true
|
||||
val vdcInflateDelegate = constructor.newInstance()
|
||||
|
||||
val args = arrayOf(String::class.java, inflateDelegateClass)
|
||||
val addDelegate =
|
||||
AppCompatDrawableManager::class.java.getDeclaredMethod("addDelegate", *args)
|
||||
addDelegate.isAccessible = true
|
||||
addDelegate.invoke(drawableManager, "vector", vdcInflateDelegate)
|
||||
} catch (e: ClassNotFoundException) {
|
||||
Log.e(TAG, "Failed to use reflection to enable proper vector scaling")
|
||||
} catch (e: NoSuchMethodException) {
|
||||
Log.e(TAG, "Failed to use reflection to enable proper vector scaling")
|
||||
} catch (e: InstantiationException) {
|
||||
Log.e(TAG, "Failed to use reflection to enable proper vector scaling")
|
||||
} catch (e: InvocationTargetException) {
|
||||
Log.e(TAG, "Failed to use reflection to enable proper vector scaling")
|
||||
} catch (e: IllegalAccessException) {
|
||||
Log.e(TAG, "Failed to use reflection to enable proper vector scaling")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun getTintedDrawable(
|
||||
res: Resources, @DrawableRes drawableResId: Int,
|
||||
@ColorRes colorResId: Int
|
||||
): Drawable {
|
||||
val drawable = res.getDrawable(drawableResId)
|
||||
val mutableDrawable = drawable.mutate()
|
||||
val color = res.getColor(colorResId)
|
||||
mutableDrawable.setTint(color)
|
||||
return mutableDrawable
|
||||
}
|
||||
|
||||
fun getDrawableForMentionChipSpan(
|
||||
context: Context,
|
||||
id: String,
|
||||
label: CharSequence,
|
||||
conversationUser: UserEntity,
|
||||
type: String,
|
||||
@XmlRes chipResource: Int,
|
||||
emojiEditText: EditText?
|
||||
): Drawable {
|
||||
val chip = ChipDrawable.createFromResource(context, chipResource)
|
||||
chip.text = EmojiCompat.get()
|
||||
.process(label)
|
||||
chip.ellipsize = TextUtils.TruncateAt.MIDDLE
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val config = context.resources.configuration
|
||||
chip.layoutDirection = config.layoutDirection
|
||||
}
|
||||
|
||||
val drawable: Int
|
||||
|
||||
val isCall = "call" == type || "calls" == type
|
||||
|
||||
val target = object : Target {
|
||||
override fun onSuccess(result: Drawable) {
|
||||
super.onSuccess(result)
|
||||
chip.chipIcon = result
|
||||
|
||||
// A hack to refresh the chip icon
|
||||
emojiEditText?.post {
|
||||
emojiEditText.setTextKeepState(
|
||||
emojiEditText.text,
|
||||
TextView.BufferType.SPANNABLE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isCall) {
|
||||
if (chipResource == R.xml.chip_you) {
|
||||
drawable = R.drawable.mention_chip
|
||||
} else {
|
||||
drawable = R.drawable.accent_circle
|
||||
}
|
||||
|
||||
Coil.load(context, drawable) {
|
||||
target(target)
|
||||
}
|
||||
} else {
|
||||
Coil.load(context, R.drawable.ic_people_group_white_24px) {
|
||||
transformations(CircleCropTransformation())
|
||||
target(target)
|
||||
}
|
||||
}
|
||||
|
||||
chip.setBounds(0, 0, chip.intrinsicWidth, chip.intrinsicHeight)
|
||||
|
||||
if (!isCall) {
|
||||
var url = ApiUtils.getUrlForAvatarWithName(
|
||||
conversationUser.baseUrl, id,
|
||||
R.dimen.avatar_size_big
|
||||
)
|
||||
if ("guests" == type || "guest" == type) {
|
||||
url = ApiUtils.getUrlForAvatarWithNameForGuests(
|
||||
conversationUser.baseUrl,
|
||||
label.toString(), R.dimen.avatar_size_big
|
||||
)
|
||||
}
|
||||
|
||||
val request = Images().getRequestForUrl(Coil.loader(), context, url, null, target, null, CircleCropTransformation())
|
||||
Coil.loader().load(request)
|
||||
}
|
||||
|
||||
return chip
|
||||
}
|
||||
|
||||
fun searchAndReplaceWithMentionSpan(
|
||||
context: Context,
|
||||
text: Spannable,
|
||||
id: String,
|
||||
label: String,
|
||||
type: String,
|
||||
conversationUser: UserEntity,
|
||||
@XmlRes chipXmlRes: Int
|
||||
): Spannable {
|
||||
|
||||
val spannableString = SpannableString(text)
|
||||
val stringText = text.toString()
|
||||
|
||||
val m = Pattern.compile(
|
||||
"@$label",
|
||||
Pattern.CASE_INSENSITIVE or Pattern.LITERAL or Pattern.MULTILINE
|
||||
)
|
||||
.matcher(spannableString)
|
||||
|
||||
val clickableSpan = object : ClickableSpan() {
|
||||
override fun onClick(widget: View) {
|
||||
EventBus.getDefault()
|
||||
.post(UserMentionClickEvent(id))
|
||||
}
|
||||
}
|
||||
|
||||
var lastStartIndex = -1
|
||||
var mentionChipSpan: Spans.MentionChipSpan
|
||||
while (m.find()) {
|
||||
val start = stringText.indexOf(m.group(), lastStartIndex)
|
||||
val end = start + m.group().length
|
||||
lastStartIndex = end
|
||||
mentionChipSpan = Spans.MentionChipSpan(
|
||||
getDrawableForMentionChipSpan(
|
||||
context,
|
||||
id, label, conversationUser, type, chipXmlRes, null
|
||||
),
|
||||
BetterImageSpan.ALIGN_CENTER, id,
|
||||
label
|
||||
)
|
||||
spannableString.setSpan(mentionChipSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
if ("user" == type && conversationUser.userId != id) {
|
||||
spannableString.setSpan(clickableSpan, start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
}
|
||||
|
||||
return spannableString
|
||||
}
|
||||
|
||||
fun searchAndColor(
|
||||
text: Spannable,
|
||||
searchText: String, @ColorInt color: Int
|
||||
): Spannable {
|
||||
|
||||
val spannableString = SpannableString(text)
|
||||
val stringText = text.toString()
|
||||
if (TextUtils.isEmpty(text) || TextUtils.isEmpty(searchText)) {
|
||||
return spannableString
|
||||
}
|
||||
|
||||
val m = Pattern.compile(
|
||||
searchText,
|
||||
Pattern.CASE_INSENSITIVE or Pattern.LITERAL or Pattern.MULTILINE
|
||||
)
|
||||
.matcher(spannableString)
|
||||
|
||||
val textSize = NextcloudTalkApplication.sharedApplication!!
|
||||
.resources
|
||||
.getDimensionPixelSize(
|
||||
R.dimen
|
||||
.chat_text_size
|
||||
)
|
||||
|
||||
var lastStartIndex = -1
|
||||
while (m.find()) {
|
||||
val start = stringText.indexOf(m.group(), lastStartIndex)
|
||||
val end = start + m.group().length
|
||||
lastStartIndex = end
|
||||
spannableString.setSpan(
|
||||
ForegroundColorSpan(color), start, end,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
spannableString.setSpan(
|
||||
StyleSpan(Typeface.BOLD), start, end,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
spannableString.setSpan(
|
||||
AbsoluteSizeSpan(textSize), start, end,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
|
||||
return spannableString
|
||||
}
|
||||
|
||||
fun getMessageSelector(
|
||||
@ColorInt normalColor: Int, @ColorInt selectedColor: Int,
|
||||
@ColorInt pressedColor: Int, @DrawableRes shape: Int
|
||||
): Drawable {
|
||||
|
||||
val vectorDrawable = ContextCompat.getDrawable(
|
||||
NextcloudTalkApplication.sharedApplication!!
|
||||
.applicationContext,
|
||||
shape
|
||||
)
|
||||
val drawable = DrawableCompat.wrap(vectorDrawable!!)
|
||||
.mutate()
|
||||
DrawableCompat.setTintList(
|
||||
drawable,
|
||||
ColorStateList(
|
||||
arrayOf(
|
||||
intArrayOf(android.R.attr.state_selected), intArrayOf(android.R.attr.state_pressed),
|
||||
intArrayOf(-android.R.attr.state_pressed, -android.R.attr.state_selected)
|
||||
),
|
||||
intArrayOf(selectedColor, pressedColor, normalColor)
|
||||
)
|
||||
)
|
||||
return drawable
|
||||
}
|
||||
}
|
@ -15,7 +15,8 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<animated-selector xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/visible"
|
||||
@ -29,11 +30,13 @@
|
||||
<transition
|
||||
android:fromId="@id/masked"
|
||||
android:toId="@id/visible"
|
||||
android:drawable="@drawable/avd_show_password" />
|
||||
android:drawable="@drawable/avd_show_password"
|
||||
tools:ignore="PrivateResource" />
|
||||
|
||||
<transition
|
||||
android:fromId="@id/visible"
|
||||
android:toId="@id/masked"
|
||||
android:drawable="@drawable/avd_hide_password" />
|
||||
android:drawable="@drawable/avd_hide_password"
|
||||
tools:ignore="PrivateResource" />
|
||||
|
||||
</animated-selector>
|
||||
|
@ -1,49 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2015 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
~ Copyright (C) 2016 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<animated-vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt">
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="NewApi">
|
||||
|
||||
<aapt:attr name="android:drawable">
|
||||
|
||||
<vector
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
android:width="24dp">
|
||||
|
||||
<path
|
||||
android:name="strike_through"
|
||||
android:pathData="@string/path_password_strike_through"
|
||||
android:strokeColor="@android:color/white"
|
||||
android:strokeWidth="1.8"
|
||||
android:strokeLineCap="square" />
|
||||
android:strokeLineCap="square"
|
||||
android:strokeWidth="1.8"/>
|
||||
|
||||
<group>
|
||||
|
||||
<clip-path
|
||||
android:name="eye_mask"
|
||||
android:pathData="@string/path_password_eye_mask_strike_through" />
|
||||
android:pathData="@string/path_password_eye_mask_strike_through"/>
|
||||
|
||||
<path
|
||||
android:name="eye"
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="@string/path_password_eye" />
|
||||
android:name="eye"
|
||||
android:pathData="@string/path_password_eye"/>
|
||||
|
||||
</group>
|
||||
|
||||
@ -56,12 +58,12 @@
|
||||
<aapt:attr name="android:animation">
|
||||
|
||||
<objectAnimator
|
||||
android:duration="@integer/show_password_duration"
|
||||
android:interpolator="@android:interpolator/fast_out_linear_in"
|
||||
android:propertyName="pathData"
|
||||
android:valueFrom="@string/path_password_eye_mask_strike_through"
|
||||
android:valueTo="@string/path_password_eye_mask_visible"
|
||||
android:duration="@integer/password_strike"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||
android:valueType="pathType" />
|
||||
android:valueType="pathType"/>
|
||||
|
||||
</aapt:attr>
|
||||
|
||||
@ -72,11 +74,11 @@
|
||||
<aapt:attr name="android:animation">
|
||||
|
||||
<objectAnimator
|
||||
android:duration="@integer/show_password_duration"
|
||||
android:interpolator="@android:interpolator/fast_out_linear_in"
|
||||
android:propertyName="trimPathEnd"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:duration="@integer/password_strike"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in" />
|
||||
android:valueTo="0"/>
|
||||
|
||||
</aapt:attr>
|
||||
|
||||
|
@ -1,49 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2015 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
~ Copyright (C) 2016 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<animated-vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt">
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="NewApi">
|
||||
|
||||
<aapt:attr name="android:drawable">
|
||||
|
||||
<vector
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
android:width="24dp">
|
||||
|
||||
<path
|
||||
android:name="strike_through"
|
||||
android:pathData="@string/path_password_strike_through"
|
||||
android:strokeColor="@android:color/white"
|
||||
android:strokeLineCap="square"
|
||||
android:strokeWidth="1.8"
|
||||
android:strokeLineCap="square" />
|
||||
android:trimPathEnd="0"/>
|
||||
|
||||
<group>
|
||||
|
||||
<clip-path
|
||||
android:name="eye_mask"
|
||||
android:pathData="@string/path_password_eye_mask_strike_through" />
|
||||
android:pathData="@string/path_password_eye_mask_visible"/>
|
||||
|
||||
<path
|
||||
android:name="eye"
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="@string/path_password_eye" />
|
||||
android:name="eye"
|
||||
android:pathData="@string/path_password_eye"/>
|
||||
|
||||
</group>
|
||||
|
||||
@ -56,12 +59,12 @@
|
||||
<aapt:attr name="android:animation">
|
||||
|
||||
<objectAnimator
|
||||
android:propertyName="pathData"
|
||||
android:valueFrom="@string/path_password_eye_mask_strike_through"
|
||||
android:valueTo="@string/path_password_eye_mask_visible"
|
||||
android:duration="@integer/password_strike"
|
||||
android:duration="@integer/hide_password_duration"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||
android:valueType="pathType" />
|
||||
android:propertyName="pathData"
|
||||
android:valueFrom="@string/path_password_eye_mask_visible"
|
||||
android:valueTo="@string/path_password_eye_mask_strike_through"
|
||||
android:valueType="pathType"/>
|
||||
|
||||
</aapt:attr>
|
||||
|
||||
@ -72,11 +75,11 @@
|
||||
<aapt:attr name="android:animation">
|
||||
|
||||
<objectAnimator
|
||||
android:duration="@integer/hide_password_duration"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||
android:propertyName="trimPathEnd"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:duration="@integer/password_strike"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in" />
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"/>
|
||||
|
||||
</aapt:attr>
|
||||
|
||||
|
@ -320,4 +320,8 @@
|
||||
<string name="nc_last_moderator_title">Could not leave conversation</string>
|
||||
<string name="nc_last_moderator">You need to promote a new moderator before you can leave %1$s.</string>
|
||||
|
||||
<!-- Non-translatable strings -->
|
||||
|
||||
<string name="path_password_strike_through" translatable="false"
|
||||
tools:override="true">M3.27,4.27L19.74,20.74</string>
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user