mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-20 12:09:45 +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'
|
apply from: 'gplay.gradle'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 29
|
||||||
buildToolsVersion '29.0.2'
|
buildToolsVersion '29.0.2'
|
||||||
@ -148,7 +154,7 @@ android {
|
|||||||
ext {
|
ext {
|
||||||
work_version = "2.3.0-alpha02"
|
work_version = "2.3.0-alpha02"
|
||||||
koin_version = "2.1.0-alpha-1"
|
koin_version = "2.1.0-alpha-1"
|
||||||
lifecycle_version = "2.2.0-beta01"
|
lifecycle_version = "2.2.0-rc01"
|
||||||
coil_version = "0.8.0"
|
coil_version = "0.8.0"
|
||||||
room_version = "2.2.0"
|
room_version = "2.2.0"
|
||||||
}
|
}
|
||||||
@ -218,8 +224,8 @@ dependencies {
|
|||||||
androidTestImplementation "androidx.work:work-testing:$work_version"
|
androidTestImplementation "androidx.work:work-testing:$work_version"
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.1.0-beta01'
|
implementation 'com.google.android.material:material:1.2.0-alpha01'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta3'
|
||||||
implementation 'com.github.vanniktech:Emoji:0.6.0'
|
implementation 'com.github.vanniktech:Emoji:0.6.0'
|
||||||
implementation group: 'androidx.emoji', name: 'emoji-bundled', version: '1.0.0'
|
implementation group: 'androidx.emoji', name: 'emoji-bundled', version: '1.0.0'
|
||||||
implementation 'org.michaelevans.colorart:library:0.0.3'
|
implementation 'org.michaelevans.colorart:library:0.0.3'
|
||||||
@ -229,7 +235,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
|
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.lifecycle:lifecycle-extensions:2.1.0"
|
||||||
|
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
@ -244,9 +250,9 @@ dependencies {
|
|||||||
implementation 'com.bluelinelabs:conductor-autodispose:3.0.0-rc2'
|
implementation 'com.bluelinelabs:conductor-autodispose:3.0.0-rc2'
|
||||||
implementation "com.github.miquelbeltran:conductor-viewmodel:1.0.3"
|
implementation "com.github.miquelbeltran:conductor-viewmodel:1.0.3"
|
||||||
|
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.14.4'
|
implementation 'com.squareup.okhttp3:okhttp:4.2.2'
|
||||||
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.4'
|
implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.2.2'
|
||||||
implementation 'com.squareup.okhttp3:logging-interceptor:3.14.4'
|
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
|
||||||
|
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
|
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
|
||||||
implementation 'com.squareup.retrofit2:adapter-rxjava2: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', {
|
androidTestImplementation('androidx.test.espresso:espresso-core:3.3.0-alpha02', {
|
||||||
exclude group: 'com.android.support', module: 'support-annotations'
|
exclude group: 'com.android.support', module: 'support-annotations'
|
||||||
})
|
})
|
||||||
findbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.9.0'
|
findbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.10.0'
|
||||||
findbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.4.6'
|
findbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.4.7'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
|
||||||
implementation 'com.github.Kennyc1012:BottomSheet:2.4.1'
|
implementation 'com.github.Kennyc1012:BottomSheet:2.4.1'
|
||||||
}
|
}
|
@ -48,7 +48,7 @@ public class MenuItem extends AbstractFlexibleItem<MenuItem.MenuItemViewHolder>
|
|||||||
this.title = title;
|
this.title = title;
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
padding = (int) DisplayUtils.convertDpToPixel(16,
|
padding = (int) DisplayUtils.INSTANCE.convertDpToPixel(16,
|
||||||
NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext());
|
NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ public class MagicOutcomingTextMessageViewHolder
|
|||||||
if (individualHashMap.get("type").equals("user") || individualHashMap.get("type")
|
if (individualHashMap.get("type").equals("user") || individualHashMap.get("type")
|
||||||
.equals("guest") || individualHashMap.get("type").equals("call")) {
|
.equals("guest") || individualHashMap.get("type").equals("call")) {
|
||||||
messageString =
|
messageString =
|
||||||
DisplayUtils.searchAndReplaceWithMentionSpan(messageText.getContext(),
|
DisplayUtils.INSTANCE.searchAndReplaceWithMentionSpan(messageText.getContext(),
|
||||||
messageString,
|
messageString,
|
||||||
individualHashMap.get("id"),
|
individualHashMap.get("id"),
|
||||||
individualHashMap.get("name"),
|
individualHashMap.get("name"),
|
||||||
@ -124,14 +124,14 @@ public class MagicOutcomingTextMessageViewHolder
|
|||||||
Resources resources = NextcloudTalkApplication.Companion.getSharedApplication().getResources();
|
Resources resources = NextcloudTalkApplication.Companion.getSharedApplication().getResources();
|
||||||
if (message.isGrouped) {
|
if (message.isGrouped) {
|
||||||
Drawable bubbleDrawable =
|
Drawable bubbleDrawable =
|
||||||
DisplayUtils.getMessageSelector(
|
DisplayUtils.INSTANCE.getMessageSelector(
|
||||||
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
||||||
resources.getColor(R.color.transparent),
|
resources.getColor(R.color.transparent),
|
||||||
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
||||||
R.drawable.shape_grouped_outcoming_message);
|
R.drawable.shape_grouped_outcoming_message);
|
||||||
ViewCompat.setBackground(bubble, bubbleDrawable);
|
ViewCompat.setBackground(bubble, bubbleDrawable);
|
||||||
} else {
|
} else {
|
||||||
Drawable bubbleDrawable = DisplayUtils.getMessageSelector(
|
Drawable bubbleDrawable = DisplayUtils.INSTANCE.getMessageSelector(
|
||||||
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
||||||
resources.getColor(R.color.transparent),
|
resources.getColor(R.color.transparent),
|
||||||
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
||||||
|
@ -96,7 +96,7 @@ public class MagicPreviewMessageViewHolder
|
|||||||
LayerDrawable layerDrawable = new LayerDrawable(layers);
|
LayerDrawable layerDrawable = new LayerDrawable(layers);
|
||||||
|
|
||||||
userAvatar.getHierarchy()
|
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) {
|
if (message.getMessageType() == ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE) {
|
||||||
// it's a preview for a Nextcloud share
|
// it's a preview for a Nextcloud share
|
||||||
messageText.setText(message.getSelectedIndividualHashMap().get("name"));
|
messageText.setText(message.getSelectedIndividualHashMap().get("name"));
|
||||||
DisplayUtils.setClickableString(message.getSelectedIndividualHashMap().get("name"),
|
DisplayUtils.INSTANCE.setClickableString(message.getSelectedIndividualHashMap().get("name"),
|
||||||
message.getSelectedIndividualHashMap().get("link"), messageText);
|
message.getSelectedIndividualHashMap().get("link"), messageText);
|
||||||
if (message.getSelectedIndividualHashMap().containsKey("mimetype")) {
|
if (message.getSelectedIndividualHashMap().containsKey("mimetype")) {
|
||||||
image.getHierarchy()
|
image.getHierarchy()
|
||||||
@ -145,10 +145,10 @@ public class MagicPreviewMessageViewHolder
|
|||||||
});
|
});
|
||||||
} else if (message.getMessageType() == ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE) {
|
} else if (message.getMessageType() == ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE) {
|
||||||
messageText.setText("GIPHY");
|
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) {
|
} else if (message.getMessageType() == ChatMessage.MessageType.SINGLE_LINK_TENOR_MESSAGE) {
|
||||||
messageText.setText("Tenor");
|
messageText.setText("Tenor");
|
||||||
DisplayUtils.setClickableString("Tenor", "https://tenor.com", messageText);
|
DisplayUtils.INSTANCE.setClickableString("Tenor", "https://tenor.com", messageText);
|
||||||
} else {
|
} else {
|
||||||
if (message.getMessageType().equals(ChatMessage.MessageType.SINGLE_LINK_IMAGE_MESSAGE)) {
|
if (message.getMessageType().equals(ChatMessage.MessageType.SINGLE_LINK_IMAGE_MESSAGE)) {
|
||||||
image.setOnClickListener(v -> {
|
image.setOnClickListener(v -> {
|
||||||
|
@ -66,7 +66,7 @@ public class MagicSystemMessageViewHolder
|
|||||||
pressedColor = normalColor;
|
pressedColor = normalColor;
|
||||||
mentionColor = resources.getColor(R.color.nc_author_text);
|
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,
|
resources.getColor(R.color.transparent), pressedColor,
|
||||||
R.drawable.shape_grouped_incoming_message);
|
R.drawable.shape_grouped_incoming_message);
|
||||||
ViewCompat.setBackground(bubble, bubbleDrawable);
|
ViewCompat.setBackground(bubble, bubbleDrawable);
|
||||||
@ -80,7 +80,7 @@ public class MagicSystemMessageViewHolder
|
|||||||
|| individualHashMap.get("type").equals("guest")
|
|| individualHashMap.get("type").equals("guest")
|
||||||
|| individualHashMap.get("type").equals("call"))) {
|
|| individualHashMap.get("type").equals("call"))) {
|
||||||
messageString =
|
messageString =
|
||||||
DisplayUtils.searchAndColor(messageString, "@" + individualHashMap.get("name"),
|
DisplayUtils.INSTANCE.searchAndColor(messageString, "@" + individualHashMap.get("name"),
|
||||||
mentionColor);
|
mentionColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ public class MentionAutocompleteCallback implements AutocompleteCallback<Mention
|
|||||||
|
|
||||||
editable.replace(start, end, replacementStringBuilder.toString() + " ");
|
editable.replace(start, end, replacementStringBuilder.toString() + " ");
|
||||||
Spans.MentionChipSpan mentionChipSpan =
|
Spans.MentionChipSpan mentionChipSpan =
|
||||||
new Spans.MentionChipSpan(DisplayUtils.getDrawableForMentionChipSpan(context,
|
new Spans.MentionChipSpan(DisplayUtils.INSTANCE.getDrawableForMentionChipSpan(context,
|
||||||
item.getId(), item.getLabel(), conversationUser, item.getSource(),
|
item.getId(), item.getLabel(), conversationUser, item.getSource(),
|
||||||
R.xml.chip_you, editText),
|
R.xml.chip_you, editText),
|
||||||
BetterImageSpan.ALIGN_CENTER,
|
BetterImageSpan.ALIGN_CENTER,
|
||||||
|
@ -1988,7 +1988,7 @@ public class CallController extends BaseController {
|
|||||||
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
|
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
|
||||||
.setOldController(avatarImageView.getController())
|
.setOldController(avatarImageView.getController())
|
||||||
.setImageRequest(
|
.setImageRequest(
|
||||||
DisplayUtils.getImageRequestForUrl(ApiUtils.getUrlForAvatarWithName(baseUrl,
|
DisplayUtils.INSTANCE.getImageRequestForUrl(ApiUtils.getUrlForAvatarWithName(baseUrl,
|
||||||
userId,
|
userId,
|
||||||
R.dimen.avatar_size_big), null))
|
R.dimen.avatar_size_big), null))
|
||||||
.build();
|
.build();
|
||||||
|
@ -415,7 +415,7 @@ public class CallNotificationController extends BaseController {
|
|||||||
avatarImageView.setVisibility(View.VISIBLE);
|
avatarImageView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
ImageRequest imageRequest =
|
ImageRequest imageRequest =
|
||||||
DisplayUtils.getImageRequestForUrl(
|
DisplayUtils.INSTANCE.getImageRequestForUrl(
|
||||||
ApiUtils.getUrlForAvatarWithName(userBeingCalled.getBaseUrl(),
|
ApiUtils.getUrlForAvatarWithName(userBeingCalled.getBaseUrl(),
|
||||||
currentConversation.getName(), R.dimen.avatar_size_very_big), null);
|
currentConversation.getName(), R.dimen.avatar_size_very_big), null);
|
||||||
|
|
||||||
@ -465,12 +465,12 @@ public class CallNotificationController extends BaseController {
|
|||||||
break;
|
break;
|
||||||
case GROUP_CONVERSATION:
|
case GROUP_CONVERSATION:
|
||||||
avatarImageView.getHierarchy()
|
avatarImageView.getHierarchy()
|
||||||
.setImage(DisplayUtils.getRoundedDrawable(
|
.setImage(DisplayUtils.INSTANCE.getRoundedDrawable(
|
||||||
context.getDrawable(R.drawable.ic_people_group_white_24px))
|
context.getDrawable(R.drawable.ic_people_group_white_24px))
|
||||||
, 100, true);
|
, 100, true);
|
||||||
case PUBLIC_CONVERSATION:
|
case PUBLIC_CONVERSATION:
|
||||||
avatarImageView.getHierarchy()
|
avatarImageView.getHierarchy()
|
||||||
.setImage(DisplayUtils.getRoundedDrawable(
|
.setImage(DisplayUtils.INSTANCE.getRoundedDrawable(
|
||||||
context.getDrawable(R.drawable.ic_people_group_white_24px))
|
context.getDrawable(R.drawable.ic_people_group_white_24px))
|
||||||
, 100, true);
|
, 100, true);
|
||||||
break;
|
break;
|
||||||
|
@ -316,7 +316,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
|
|||||||
) {
|
) {
|
||||||
val avatarSize = DisplayUtils.convertDpToPixel(
|
val avatarSize = DisplayUtils.convertDpToPixel(
|
||||||
conversationVoiceCallMenuItem?.icon!!
|
conversationVoiceCallMenuItem?.icon!!
|
||||||
.intrinsicWidth.toFloat(), activity
|
.intrinsicWidth.toFloat(), activity!!
|
||||||
)
|
)
|
||||||
.toInt()
|
.toInt()
|
||||||
|
|
||||||
@ -384,13 +384,13 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
|
|||||||
|
|
||||||
adapter = MessagesListAdapter(
|
adapter = MessagesListAdapter(
|
||||||
conversationUser?.userId, messageHolders, ImageLoader { imageView, url, payload ->
|
conversationUser?.userId, messageHolders, ImageLoader { imageView, url, payload ->
|
||||||
val draweeController = Fresco.newDraweeControllerBuilder()
|
/*val draweeController = Fresco.newDraweeControllerBuilder()
|
||||||
.setImageRequest(DisplayUtils.getImageRequestForUrl(url, conversationUser))
|
.setImageRequest(DisplayUtils.getImageRequestForUrl(url, conversationUser))
|
||||||
.setControllerListener(DisplayUtils.getImageControllerListener(imageView))
|
.setControllerListener(DisplayUtils.getImageControllerListener(imageView))
|
||||||
.setOldController(imageView.controller)
|
.setOldController(imageView.controller)
|
||||||
.setAutoPlayAnimations(true)
|
.setAutoPlayAnimations(true)
|
||||||
.build()
|
.build()
|
||||||
imageView.controller = draweeController
|
imageView.controller = draweeController*/
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
messagesListView?.visibility = View.VISIBLE
|
messagesListView?.visibility = View.VISIBLE
|
||||||
@ -1136,9 +1136,8 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
|
|||||||
isFromTheFuture: Boolean,
|
isFromTheFuture: Boolean,
|
||||||
timeout: Int
|
timeout: Int
|
||||||
) {
|
) {
|
||||||
val xChatLastGivenHeader: String? = response.headers()
|
val xChatLastGivenHeader: String? = response.headers().get("X-Chat-Last-Given")
|
||||||
.get("X-Chat-Last-Given")
|
if (response.headers().size > 0 && !TextUtils.isEmpty(xChatLastGivenHeader)) {
|
||||||
if (response.headers().size() > 0 && !TextUtils.isEmpty(xChatLastGivenHeader)) {
|
|
||||||
|
|
||||||
val header = Integer.parseInt(xChatLastGivenHeader!!)
|
val header = Integer.parseInt(xChatLastGivenHeader!!)
|
||||||
if (header > 0) {
|
if (header > 0) {
|
||||||
|
@ -417,10 +417,10 @@ public class OperationsMenuController extends BaseController {
|
|||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
|
|
||||||
if (everythingOK) {
|
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));
|
.ic_check_circle_black_24dp, R.color.nc_darkGreen));
|
||||||
} else {
|
} else {
|
||||||
resultImageView.setImageDrawable(DisplayUtils.getTintedDrawable(getResources(), R.drawable
|
resultImageView.setImageDrawable(DisplayUtils.INSTANCE.getTintedDrawable(getResources(), R.drawable
|
||||||
.ic_cancel_black_24dp, R.color.nc_darkRed));
|
.ic_cancel_black_24dp, R.color.nc_darkRed));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,7 +448,7 @@ public class OperationsMenuController extends BaseController {
|
|||||||
if (everythingOK) {
|
if (everythingOK) {
|
||||||
eventBus.post(new BottomSheetLockEvent(true, 2500, true, true));
|
eventBus.post(new BottomSheetLockEvent(true, 2500, true, true));
|
||||||
} else {
|
} else {
|
||||||
resultImageView.setImageDrawable(DisplayUtils.getTintedDrawable(getResources(), R.drawable
|
resultImageView.setImageDrawable(DisplayUtils.INSTANCE.getTintedDrawable(getResources(), R.drawable
|
||||||
.ic_cancel_black_24dp, R.color.nc_darkRed));
|
.ic_cancel_black_24dp, R.color.nc_darkRed));
|
||||||
okButton.setOnClickListener(
|
okButton.setOnClickListener(
|
||||||
v -> eventBus.post(new BottomSheetLockEvent(true, 0, operationCode != 99
|
v -> eventBus.post(new BottomSheetLockEvent(true, 0, operationCode != 99
|
||||||
|
@ -25,4 +25,8 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
public class UserMentionClickEvent {
|
public class UserMentionClickEvent {
|
||||||
public final String userId;
|
public final String userId;
|
||||||
|
|
||||||
|
public UserMentionClickEvent(String userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -438,7 +438,7 @@ public class NotificationWorker extends Worker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImageRequest imageRequest =
|
ImageRequest imageRequest =
|
||||||
DisplayUtils.getImageRequestForUrl(avatarUrl, null);
|
DisplayUtils.INSTANCE.getImageRequestForUrl(avatarUrl, null);
|
||||||
ImagePipeline imagePipeline = Fresco.getImagePipeline();
|
ImagePipeline imagePipeline = Fresco.getImagePipeline();
|
||||||
DataSource<CloseableReference<CloseableImage>> dataSource =
|
DataSource<CloseableReference<CloseableImage>> dataSource =
|
||||||
imagePipeline.fetchDecodedImage(imageRequest, context);
|
imagePipeline.fetchDecodedImage(imageRequest, context);
|
||||||
|
@ -27,7 +27,7 @@ import org.parceler.Parcel;
|
|||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
@Data
|
@Data
|
||||||
@JsonObject()
|
@JsonObject
|
||||||
public class UserProfileData {
|
public class UserProfileData {
|
||||||
@JsonField(name = "display-name")
|
@JsonField(name = "display-name")
|
||||||
public String displayName;
|
public String displayName;
|
||||||
|
@ -117,7 +117,7 @@ fun createOkHttpClient(
|
|||||||
// Trust own CA and all self-signed certs
|
// Trust own CA and all self-signed certs
|
||||||
httpClient.sslSocketFactory(sslSocketFactoryCompat, magicTrustManager)
|
httpClient.sslSocketFactory(sslSocketFactoryCompat, magicTrustManager)
|
||||||
httpClient.retryOnConnectionFailure(true)
|
httpClient.retryOnConnectionFailure(true)
|
||||||
httpClient.hostnameVerifier(magicTrustManager.getHostnameVerifier(OkHostnameVerifier.INSTANCE))
|
httpClient.hostnameVerifier(magicTrustManager.getHostnameVerifier(OkHostnameVerifier))
|
||||||
|
|
||||||
httpClient.dispatcher(dispatcher)
|
httpClient.dispatcher(dispatcher)
|
||||||
if (Proxy.NO_PROXY != proxy) {
|
if (Proxy.NO_PROXY != proxy) {
|
||||||
|
@ -122,7 +122,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
|
|||||||
?.let {
|
?.let {
|
||||||
DisplayUtils.convertDpToPixel(
|
DisplayUtils.convertDpToPixel(
|
||||||
it,
|
it,
|
||||||
activity
|
activity!!
|
||||||
)
|
)
|
||||||
.toInt()
|
.toInt()
|
||||||
}
|
}
|
||||||
@ -419,7 +419,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
|
|||||||
negativeButton(R.string.nc_cancel)
|
negativeButton(R.string.nc_cancel)
|
||||||
icon(
|
icon(
|
||||||
drawable = DisplayUtils.getTintedDrawable(
|
drawable = DisplayUtils.getTintedDrawable(
|
||||||
resources, drawable
|
resources!!, drawable
|
||||||
.ic_delete_grey600_24dp, R.color.nc_darkRed
|
.ic_delete_grey600_24dp, R.color.nc_darkRed
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -34,7 +34,7 @@ class Images {
|
|||||||
context: Context,
|
context: Context,
|
||||||
url: String,
|
url: String,
|
||||||
userEntity:
|
userEntity:
|
||||||
UserEntity,
|
UserEntity?,
|
||||||
target: Target?,
|
target: Target?,
|
||||||
lifecycleOwner: LifecycleOwner?,
|
lifecycleOwner: LifecycleOwner?,
|
||||||
vararg transformations: Transformation
|
vararg transformations: Transformation
|
||||||
@ -50,7 +50,7 @@ class Images {
|
|||||||
target(target)
|
target(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.startsWith(userEntity.baseUrl) && url.contains(
|
if (userEntity != null && url.startsWith(userEntity.baseUrl) && url.contains(
|
||||||
"index.php/core/preview?fileId="
|
"index.php/core/preview?fileId="
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
@ -45,14 +45,14 @@ class NetworkUtils {
|
|||||||
.header("User-Agent", ApiUtils.getUserAgent())
|
.header("User-Agent", ApiUtils.getUserAgent())
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.header("OCS-APIRequest", "true")
|
.header("OCS-APIRequest", "true")
|
||||||
.method(original.method(), original.body())
|
.method(original.method, original.body)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val response = chain.proceed(request)
|
val response = chain.proceed(request)
|
||||||
|
|
||||||
if (request.url().encodedPath().contains("/avatar/")) {
|
if (request.url.encodedPath.contains("/avatar/")) {
|
||||||
AvatarStatusCodeHolder.getInstance()
|
AvatarStatusCodeHolder.getInstance()
|
||||||
.statusCode = response.code()
|
.statusCode = response.code
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@ -68,7 +68,7 @@ class NetworkUtils {
|
|||||||
route: Route?,
|
route: Route?,
|
||||||
response: Response
|
response: Response
|
||||||
): Request? {
|
): Request? {
|
||||||
if (response.request().header(authenticatorType) != null) {
|
if (response.request.header(authenticatorType) != null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,13 +77,13 @@ class NetworkUtils {
|
|||||||
var attemptsCount = 0
|
var attemptsCount = 0
|
||||||
|
|
||||||
|
|
||||||
while ({ countedResponse = countedResponse?.priorResponse(); countedResponse }() != null) {
|
while ({ countedResponse = countedResponse?.priorResponse; countedResponse }() != null) {
|
||||||
attemptsCount++
|
attemptsCount++
|
||||||
if (attemptsCount == 3) {
|
if (attemptsCount == 3) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return response.request()
|
return response.request
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.header(authenticatorType, credentials)
|
.header(authenticatorType, credentials)
|
||||||
.build()
|
.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.
|
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
|
<item
|
||||||
android:id="@+id/visible"
|
android:id="@+id/visible"
|
||||||
@ -29,11 +30,13 @@
|
|||||||
<transition
|
<transition
|
||||||
android:fromId="@id/masked"
|
android:fromId="@id/masked"
|
||||||
android:toId="@id/visible"
|
android:toId="@id/visible"
|
||||||
android:drawable="@drawable/avd_show_password" />
|
android:drawable="@drawable/avd_show_password"
|
||||||
|
tools:ignore="PrivateResource" />
|
||||||
|
|
||||||
<transition
|
<transition
|
||||||
android:fromId="@id/visible"
|
android:fromId="@id/visible"
|
||||||
android:toId="@id/masked"
|
android:toId="@id/masked"
|
||||||
android:drawable="@drawable/avd_hide_password" />
|
android:drawable="@drawable/avd_hide_password"
|
||||||
|
tools:ignore="PrivateResource" />
|
||||||
|
|
||||||
</animated-selector>
|
</animated-selector>
|
||||||
|
@ -1,49 +1,51 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
Copyright 2015 Google Inc.
|
~ Copyright (C) 2016 The Android Open Source Project
|
||||||
|
~
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
~ you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
Unless required by applicable law or agreed to in writing, software
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
~ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<animated-vector
|
<animated-vector
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
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">
|
<aapt:attr name="android:drawable">
|
||||||
|
|
||||||
<vector
|
<vector
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:width="24dp">
|
||||||
|
|
||||||
<path
|
<path
|
||||||
android:name="strike_through"
|
android:name="strike_through"
|
||||||
android:pathData="@string/path_password_strike_through"
|
android:pathData="@string/path_password_strike_through"
|
||||||
android:strokeColor="@android:color/white"
|
android:strokeColor="@android:color/white"
|
||||||
android:strokeWidth="1.8"
|
android:strokeLineCap="square"
|
||||||
android:strokeLineCap="square" />
|
android:strokeWidth="1.8"/>
|
||||||
|
|
||||||
<group>
|
<group>
|
||||||
|
|
||||||
<clip-path
|
<clip-path
|
||||||
android:name="eye_mask"
|
android:name="eye_mask"
|
||||||
android:pathData="@string/path_password_eye_mask_strike_through" />
|
android:pathData="@string/path_password_eye_mask_strike_through"/>
|
||||||
|
|
||||||
<path
|
<path
|
||||||
android:name="eye"
|
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="@string/path_password_eye" />
|
android:name="eye"
|
||||||
|
android:pathData="@string/path_password_eye"/>
|
||||||
|
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
@ -56,12 +58,12 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="@integer/show_password_duration"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_linear_in"
|
||||||
android:propertyName="pathData"
|
android:propertyName="pathData"
|
||||||
android:valueFrom="@string/path_password_eye_mask_strike_through"
|
android:valueFrom="@string/path_password_eye_mask_strike_through"
|
||||||
android:valueTo="@string/path_password_eye_mask_visible"
|
android:valueTo="@string/path_password_eye_mask_visible"
|
||||||
android:duration="@integer/password_strike"
|
android:valueType="pathType"/>
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"
|
|
||||||
android:valueType="pathType" />
|
|
||||||
|
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
|
|
||||||
@ -72,11 +74,11 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="@integer/show_password_duration"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_linear_in"
|
||||||
android:propertyName="trimPathEnd"
|
android:propertyName="trimPathEnd"
|
||||||
android:valueFrom="1"
|
android:valueFrom="1"
|
||||||
android:valueTo="0"
|
android:valueTo="0"/>
|
||||||
android:duration="@integer/password_strike"
|
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in" />
|
|
||||||
|
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
|
|
||||||
|
@ -1,49 +1,52 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
Copyright 2015 Google Inc.
|
~ Copyright (C) 2016 The Android Open Source Project
|
||||||
|
~
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
~ you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
Unless required by applicable law or agreed to in writing, software
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
~ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<animated-vector
|
<animated-vector
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
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">
|
<aapt:attr name="android:drawable">
|
||||||
|
|
||||||
<vector
|
<vector
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:width="24dp">
|
||||||
|
|
||||||
<path
|
<path
|
||||||
android:name="strike_through"
|
android:name="strike_through"
|
||||||
android:pathData="@string/path_password_strike_through"
|
android:pathData="@string/path_password_strike_through"
|
||||||
android:strokeColor="@android:color/white"
|
android:strokeColor="@android:color/white"
|
||||||
|
android:strokeLineCap="square"
|
||||||
android:strokeWidth="1.8"
|
android:strokeWidth="1.8"
|
||||||
android:strokeLineCap="square" />
|
android:trimPathEnd="0"/>
|
||||||
|
|
||||||
<group>
|
<group>
|
||||||
|
|
||||||
<clip-path
|
<clip-path
|
||||||
android:name="eye_mask"
|
android:name="eye_mask"
|
||||||
android:pathData="@string/path_password_eye_mask_strike_through" />
|
android:pathData="@string/path_password_eye_mask_visible"/>
|
||||||
|
|
||||||
<path
|
<path
|
||||||
android:name="eye"
|
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="@string/path_password_eye" />
|
android:name="eye"
|
||||||
|
android:pathData="@string/path_password_eye"/>
|
||||||
|
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
@ -56,12 +59,12 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:propertyName="pathData"
|
android:duration="@integer/hide_password_duration"
|
||||||
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: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>
|
</aapt:attr>
|
||||||
|
|
||||||
@ -72,11 +75,11 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
|
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
|
android:duration="@integer/hide_password_duration"
|
||||||
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="trimPathEnd"
|
android:propertyName="trimPathEnd"
|
||||||
android:valueFrom="1"
|
android:valueFrom="0"
|
||||||
android:valueTo="0"
|
android:valueTo="1"/>
|
||||||
android:duration="@integer/password_strike"
|
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in" />
|
|
||||||
|
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
|
|
||||||
|
@ -320,4 +320,8 @@
|
|||||||
<string name="nc_last_moderator_title">Could not leave conversation</string>
|
<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>
|
<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>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user