Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2019-10-25 13:22:14 +02:00
parent e11705f230
commit 059aaedca8
23 changed files with 540 additions and 542 deletions

View File

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

View File

@ -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());
}

View File

@ -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),

View File

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

View File

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

View File

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

View File

@ -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();

View File

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

View File

@ -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) {

View File

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

View File

@ -25,4 +25,8 @@ import lombok.Data;
@Data
public class UserMentionClickEvent {
public final String userId;
public UserMentionClickEvent(String userId) {
this.userId = userId;
}
}

View File

@ -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);

View File

@ -27,7 +27,7 @@ import org.parceler.Parcel;
@Parcel
@Data
@JsonObject()
@JsonObject
public class UserProfileData {
@JsonField(name = "display-name")
public String displayName;

View File

@ -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) {

View File

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

View File

@ -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="
)
) {

View File

@ -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()

View File

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

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

View File

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

View File

@ -1,38 +1,40 @@
<?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>
@ -41,8 +43,8 @@
android:pathData="@string/path_password_eye_mask_strike_through"/>
<path
android:name="eye"
android:fillColor="@android:color/white"
android:name="eye"
android:pathData="@string/path_password_eye"/>
</group>
@ -56,11 +58,11 @@
<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"/>
</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>

View File

@ -1,48 +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: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:name="eye"
android:pathData="@string/path_password_eye"/>
</group>
@ -56,11 +59,11 @@
<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: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>

View File

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