From e666159cce345cdeb552377372cb6c2c3ceb817a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D1=87?= =?UTF-8?q?=D0=B8=D0=BD=D1=81=D0=BA=D0=B8=D0=B8=CC=86?= Date: Tue, 5 Nov 2019 17:43:43 +0300 Subject: [PATCH 1/3] rewrite getSpannedTextWithUrls: make it extension and add new arguments --- .../roboswag/components/utils/UiUtils.java | 300 ------------------ .../roboswag/components/utils/UiUtils.kt | 248 +++++++++++++++ ...{ColoredUrlSpan.java => ColoredUrlSpan.kt} | 26 +- .../components/utils/spans/PhoneSpan.java | 39 --- .../components/utils/spans/PhoneSpan.kt | 26 ++ .../components/utils/spans/SpanUtils.kt | 40 +++ .../components/utils/spans/TypefaceSpan.java | 55 ---- .../components/utils/spans/TypefaceSpan.kt | 44 +++ .../utils/spans/URLSpanWithoutUnderline.kt | 12 + .../components/views/MaterialLoadingBar.java | 4 +- .../views/MaterialProgressDrawable.java | 2 +- .../components/views/TypefacedTextView.java | 4 +- .../views/internal/AttributesUtils.java | 2 +- 13 files changed, 383 insertions(+), 419 deletions(-) delete mode 100644 utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java create mode 100644 utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt rename utils/src/main/java/ru/touchin/roboswag/components/utils/spans/{ColoredUrlSpan.java => ColoredUrlSpan.kt} (61%) delete mode 100644 utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.java create mode 100644 utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.kt create mode 100644 utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt delete mode 100644 utils/src/main/java/ru/touchin/roboswag/components/utils/spans/TypefaceSpan.java create mode 100644 utils/src/main/java/ru/touchin/roboswag/components/utils/spans/TypefaceSpan.kt create mode 100644 utils/src/main/java/ru/touchin/roboswag/components/utils/spans/URLSpanWithoutUnderline.kt diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java b/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java deleted file mode 100644 index d3cd40b..0000000 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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. - * - */ - -package ru.touchin.roboswag.components.utils; - -import android.app.Activity; -import android.app.Application; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.os.Build; -import android.text.Html; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.style.URLSpan; -import android.text.util.Linkify; -import android.util.DisplayMetrics; -import android.util.TypedValue; -import android.view.Display; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.inputmethod.InputMethodManager; - -import androidx.annotation.LayoutRes; -import androidx.annotation.NonNull; - -/** - * Created by Gavriil Sitnikov on 13/11/2015. - * General utilities related to UI (Inflation, Views, Metrics, Activities etc.). - */ -public final class UiUtils { - - /** - * Method to inflate view with right layout parameters based on container and add inflated view as a child to it. - * - * @param layoutId Id of layout resource; - * @param parent Container to rightly resolve layout parameters of view in XML; - * @return Inflated view. - */ - @NonNull - public static View inflateAndAdd(@LayoutRes final int layoutId, @NonNull final ViewGroup parent) { - LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, true); - return parent.getChildAt(parent.getChildCount() - 1); - } - - /** - * Method to inflate view with right layout parameters based on container but not adding inflated view as a child to it. - * - * @param layoutId Id of layout resource; - * @param parent Container to rightly resolve layout parameters of view in XML; - * @return Inflated view. - */ - @NonNull - public static View inflate(@LayoutRes final int layoutId, @NonNull final ViewGroup parent) { - return LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false); - } - - /** - * Convert text with 'href' tags and raw links to spanned text with clickable URLSpan. - */ - @NonNull - public static Spanned getSpannedTextWithUrls(@NonNull final String text) { - final Spannable spannableText = new SpannableString(Html.fromHtml(text)); - // Linkify removes all previous URLSpan's, we need to save all created spans for reapply after Linkify - final URLSpan[] spans = spannableText.getSpans(0, text.length(), URLSpan.class); - final int[] starts = new int[spans.length]; - final int[] ends = new int[spans.length]; - for (int index = 0; index < spans.length; index++) { - starts[index] = spannableText.getSpanStart(spans[index]); - ends[index] = spannableText.getSpanEnd(spans[index]); - } - Linkify.addLinks(spannableText, Linkify.WEB_URLS); - for (int index = 0; index < spans.length; index++) { - spannableText.setSpan(spans[index], starts[index], ends[index], Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - return spannableText; - } - - private UiUtils() { - } - - /** - * Utilities methods related to metrics. - */ - public static class OfMetrics { - - private static final int MAX_METRICS_TRIES_COUNT = 5; - - /** - * Returns right metrics with non-zero height/width. - * It is common bug when metrics are calling at {@link Application#onCreate()} method and it returns metrics with zero height/width. - * - * @param context {@link Context} of metrics; - * @return {@link DisplayMetrics}. - */ - @SuppressWarnings("BusyWait") - @NonNull - public static DisplayMetrics getDisplayMetrics(@NonNull final Context context) { - DisplayMetrics result = context.getResources().getDisplayMetrics(); - // it is needed to avoid bug with invalid metrics when user restore application from other application - int metricsTryNumber = 0; - while (metricsTryNumber < MAX_METRICS_TRIES_COUNT && (result.heightPixels <= 0 || result.widthPixels <= 0)) { - try { - Thread.sleep(500); - } catch (final InterruptedException ignored) { - return result; - } - result = context.getResources().getDisplayMetrics(); - metricsTryNumber++; - } - return result; - } - - /** - * Simply converts DP to pixels. - * - * @param context {@link Context} of metrics; - * @param sizeInDp Size in DP; - * @return Size in pixels. - */ - public static float dpToPixels(@NonNull final Context context, final float sizeInDp) { - return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, sizeInDp, getDisplayMetrics(context)); - } - - public static int pixelsToDp(@NonNull final Context context, final int pixels) { - return (int) (pixels * getDisplayMetrics(context).density + 0.5f); - } - - private OfMetrics() { - } - - } - - /** - * Utilities methods related to activities and it'sintents. - */ - public static class OfActivities { - - /** - * Returns action bar (on top like toolbar or appbar) common height (56dp). - * - * @param activity Activity of action bar; - * @return Height of action bar. - */ - public static int getActionBarHeight(@NonNull final Activity activity) { - return (int) OfMetrics.dpToPixels(activity, 56); - } - - /** - * Returns status bar (on top where system info is) common height. - * - * @param activity Activity of status bar; - * @return Height of status bar. - */ - public static int getStatusBarHeight(@NonNull final Activity activity) { - final int resourceId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android"); - return resourceId > 0 ? activity.getResources().getDimensionPixelSize(resourceId) : 0; - } - - /** - * Returns navigation bar (on bottom where system buttons are) common height. - * Be aware that some devices have no software keyboard (check it by {@link #hasSoftKeys(Activity)}) but this method will return you - * size like they are showed. - * - * @param activity Activity of navigation bar; - * @return Height of navigation bar. - */ - public static int getNavigationBarHeight(@NonNull final Activity activity) { - if (hasSoftKeys(activity)) { - final int resourceId = activity.getResources().getIdentifier("navigation_bar_height", "dimen", "android"); - return resourceId > 0 ? activity.getResources().getDimensionPixelSize(resourceId) : 0; - } - return 0; - } - - /** - * Returns if device has software keyboard at navigation bar or not. - * - * @param activity Activity of navigation bar; - * @return True if software keyboard is showing at navigation bar. - */ - //http://stackoverflow.com/questions/14853039/how-to-tell-whether-an-android-device-has-hard-keys/14871974#14871974 - public static boolean hasSoftKeys(@NonNull final Activity activity) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - final Display display = activity.getWindowManager().getDefaultDisplay(); - - final DisplayMetrics realDisplayMetrics = new DisplayMetrics(); - display.getRealMetrics(realDisplayMetrics); - - final DisplayMetrics displayMetrics = new DisplayMetrics(); - display.getMetrics(displayMetrics); - - return (realDisplayMetrics.widthPixels - displayMetrics.widthPixels) > 0 - || (realDisplayMetrics.heightPixels - displayMetrics.heightPixels) > 0; - } - - final boolean hasMenuKey = ViewConfiguration.get(activity).hasPermanentMenuKey(); - final boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK); - return !hasMenuKey && !hasBackKey; - } - - /** - * Returns if {@link Intent} could be handled by system. - * - * @param context {@link Context} of application; - * @param intent {@link Intent} to be handled; - * @return True if there are handlers for {@link Intent} (e.g. browser could handle URI intent). - */ - public static boolean isIntentAbleToHandle(@NonNull final Context context, @NonNull final Intent intent) { - return !context.getPackageManager().queryIntentActivities(intent, 0).isEmpty(); - } - - private OfActivities() { - } - - } - - /** - * Utilities methods related to views. - */ - public static class OfViews { - - /** - * Returns string representation of {@link View}'s ID. - * - * @param view {@link View} to get ID from; - * @return Readable ID. - */ - @NonNull - public static String getViewIdString(@NonNull final View view) { - try { - return view.getResources().getResourceName(view.getId()); - } catch (final Resources.NotFoundException exception) { - return String.valueOf(view.getId()); - } - } - - /** - * Hides device keyboard for target activity. - */ - public static void hideSoftInput(@NonNull final Activity activity) { - final View focusedView = activity.getCurrentFocus(); - if (focusedView != null) { - hideSoftInput(focusedView); - } - } - - /** - * Hides device keyboard for target view. - */ - public static void hideSoftInput(@NonNull final View view) { - view.clearFocus(); - final InputMethodManager inputManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - if (inputManager != null) { - inputManager.hideSoftInputFromWindow(view.getWindowToken(), 0); - } - } - - /** - * Shows device keyboard over {@link Activity} and focuses {@link View}. - * Do NOT use it if keyboard is over {@link android.app.Dialog} - it won't work as they have different {@link Activity#getWindow()}. - * Do NOT use it if you are not sure that view is already added on screen. - * - * @param view View to get focus for input from keyboard. - */ - public static void showSoftInput(@NonNull final View view) { - view.requestFocus(); - final InputMethodManager inputManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - if (inputManager != null) { - inputManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); - } - } - - private OfViews() { - } - - } - -} diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt new file mode 100644 index 0000000..45339d2 --- /dev/null +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) + * + * This file is part of RoboSwag library. + * + * 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. + * + */ + +package ru.touchin.roboswag.components.utils + +import android.app.Activity +import android.app.Application +import android.content.Context +import android.content.Intent +import android.content.res.Resources +import android.os.Build +import android.util.DisplayMetrics +import android.util.TypedValue +import android.view.KeyCharacterMap +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewConfiguration +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import androidx.annotation.LayoutRes +import ru.touchin.roboswag.components.utils.spans.getSpannedTextWithUrls + +/** + * Created by Gavriil Sitnikov on 13/11/2015. + * General utilities related to UI (Inflation, Views, Metrics, Activities etc.). + */ +object UiUtils { + + /** + * Method to inflate view with right layout parameters based on container and add inflated view as a child to it. + * + * @param layoutId Id of layout resource; + * @param parent Container to rightly resolve layout parameters of view in XML; + * @return Inflated view. + */ + fun inflateAndAdd(@LayoutRes layoutId: Int, parent: ViewGroup): View { + LayoutInflater.from(parent.context).inflate(layoutId, parent, true) + return parent.getChildAt(parent.childCount - 1) + } + + /** + * Method to inflate view with right layout parameters based on container but not adding inflated view as a child to it. + * + * @param layoutId Id of layout resource; + * @param parent Container to rightly resolve layout parameters of view in XML; + * @return Inflated view. + */ + fun inflate(@LayoutRes layoutId: Int, parent: ViewGroup): View = LayoutInflater.from(parent.context).inflate(layoutId, parent, false) + + /** + * Convert text with 'href' tags and raw links to spanned text with clickable URLSpan. + */ + @Deprecated( + "use extension in SpanUtils", + ReplaceWith("text.getSpannedTextWithUrls()", "ru.touchin.roboswag.components.utils.spans.getSpannedTextWithUrls") + ) + fun getSpannedTextWithUrls(text: String) = text.getSpannedTextWithUrls() + + /** + * Utilities methods related to metrics. + */ + object OfMetrics { + + private const val MAX_METRICS_TRIES_COUNT = 5 + + /** + * Returns right metrics with non-zero height/width. + * It is common bug when metrics are calling at [Application.onCreate] method and it returns metrics with zero height/width. + * + * @param context [Context] of metrics; + * @return [DisplayMetrics]. + */ + fun getDisplayMetrics(context: Context): DisplayMetrics { + var result = context.resources.displayMetrics + // it is needed to avoid bug with invalid metrics when user restore application from other application + var metricsTryNumber = 0 + while (metricsTryNumber < MAX_METRICS_TRIES_COUNT && (result.heightPixels <= 0 || result.widthPixels <= 0)) { + try { + Thread.sleep(500) + } catch (ignored: InterruptedException) { + return result + } + + result = context.resources.displayMetrics + metricsTryNumber++ + } + return result + } + + /** + * Simply converts DP to pixels. + * + * @param context [Context] of metrics; + * @param sizeInDp Size in DP; + * @return Size in pixels. + */ + fun dpToPixels(context: Context, sizeInDp: Float): Float = + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, sizeInDp, getDisplayMetrics(context)) + + fun pixelsToDp(context: Context, pixels: Int): Int = (pixels * getDisplayMetrics(context).density + 0.5f).toInt() + + } + + /** + * Utilities methods related to activities and it'sintents. + */ + object OfActivities { + + /** + * Returns action bar (on top like toolbar or appbar) common height (56dp). + * + * @param activity Activity of action bar; + * @return Height of action bar. + */ + fun getActionBarHeight(activity: Activity): Int = OfMetrics.dpToPixels(activity, 56f).toInt() + + /** + * Returns status bar (on top where system info is) common height. + * + * @param activity Activity of status bar; + * @return Height of status bar. + */ + fun getStatusBarHeight(activity: Activity): Int { + val resourceId = activity.resources.getIdentifier("status_bar_height", "dimen", "android") + return if (resourceId > 0) activity.resources.getDimensionPixelSize(resourceId) else 0 + } + + /** + * Returns navigation bar (on bottom where system buttons are) common height. + * Be aware that some devices have no software keyboard (check it by [.hasSoftKeys]) but this method will return you + * size like they are showed. + * + * @param activity Activity of navigation bar; + * @return Height of navigation bar. + */ + fun getNavigationBarHeight(activity: Activity): Int { + if (hasSoftKeys(activity)) { + val resourceId = activity.resources.getIdentifier("navigation_bar_height", "dimen", "android") + return if (resourceId > 0) activity.resources.getDimensionPixelSize(resourceId) else 0 + } + return 0 + } + + /** + * Returns if device has software keyboard at navigation bar or not. + * + * @param activity Activity of navigation bar; + * @return True if software keyboard is showing at navigation bar. + */ + //http://stackoverflow.com/questions/14853039/how-to-tell-whether-an-android-device-has-hard-keys/14871974#14871974 + fun hasSoftKeys(activity: Activity): Boolean { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + val display = activity.windowManager.defaultDisplay + + val realDisplayMetrics = DisplayMetrics() + display.getRealMetrics(realDisplayMetrics) + + val displayMetrics = DisplayMetrics() + display.getMetrics(displayMetrics) + + return realDisplayMetrics.widthPixels - displayMetrics.widthPixels > 0 || realDisplayMetrics.heightPixels - displayMetrics.heightPixels > 0 + } + + val hasMenuKey = ViewConfiguration.get(activity).hasPermanentMenuKey() + val hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK) + return !hasMenuKey && !hasBackKey + } + + /** + * Returns if [Intent] could be handled by system. + * + * @param context [Context] of application; + * @param intent [Intent] to be handled; + * @return True if there are handlers for [Intent] (e.g. browser could handle URI intent). + */ + fun isIntentAbleToHandle(context: Context, intent: Intent): Boolean = context.packageManager.queryIntentActivities(intent, 0).isNotEmpty() + + } + + /** + * Utilities methods related to views. + */ + object OfViews { + + /** + * Returns string representation of [View]'s ID. + * + * @param view [View] to get ID from; + * @return Readable ID. + */ + fun getViewIdString(view: View): String = try { + view.resources.getResourceName(view.id) + } catch (exception: Resources.NotFoundException) { + view.id.toString() + } + + /** + * Hides device keyboard for target activity. + */ + fun hideSoftInput(activity: Activity) { + val focusedView = activity.currentFocus + if (focusedView != null) { + hideSoftInput(focusedView) + } + } + + /** + * Hides device keyboard for target view. + */ + fun hideSoftInput(view: View) { + view.clearFocus() + val inputManager = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + inputManager.hideSoftInputFromWindow(view.windowToken, 0) + } + + /** + * Shows device keyboard over [Activity] and focuses [View]. + * Do NOT use it if keyboard is over [android.app.Dialog] - it won't work as they have different [Activity.getWindow]. + * Do NOT use it if you are not sure that view is already added on screen. + * + * @param view View to get focus for input from keyboard. + */ + fun showSoftInput(view: View) { + view.requestFocus() + val inputManager = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + inputManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) + } + + } + +} diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/ColoredUrlSpan.java b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/ColoredUrlSpan.kt similarity index 61% rename from utils/src/main/java/ru/touchin/roboswag/components/utils/spans/ColoredUrlSpan.java rename to utils/src/main/java/ru/touchin/roboswag/components/utils/spans/ColoredUrlSpan.kt index c484f91..251479c 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/ColoredUrlSpan.java +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/ColoredUrlSpan.kt @@ -17,12 +17,10 @@ * */ -package ru.touchin.roboswag.components.utils.spans; +package ru.touchin.roboswag.components.utils.spans -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import android.text.TextPaint; -import android.text.style.URLSpan; +import android.text.TextPaint +import androidx.annotation.ColorInt /** * Created by Anton Arhipov on 05/07/2017. @@ -32,21 +30,11 @@ import android.text.style.URLSpan; * and * textView.setText(spannableString, TextView.BufferType.SPANNABLE); */ -public class ColoredUrlSpan extends URLSpan { +class ColoredUrlSpan(@ColorInt private val textColor: Int, url: String) : URLSpanWithoutUnderline(url) { - @ColorInt - private final int textColor; - - public ColoredUrlSpan(@ColorInt final int textColor, @NonNull final String url) { - super(url); - this.textColor = textColor; - } - - @Override - public void updateDrawState(@NonNull final TextPaint ds) { - super.updateDrawState(ds); - ds.setUnderlineText(false); - ds.setColor(textColor); + override fun updateDrawState(ds: TextPaint) { + super.updateDrawState(ds) + ds.color = textColor } } diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.java b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.java deleted file mode 100644 index 2140230..0000000 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.java +++ /dev/null @@ -1,39 +0,0 @@ -package ru.touchin.roboswag.components.utils.spans; - -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.net.Uri; -import androidx.annotation.NonNull; -import android.text.TextPaint; -import android.text.style.URLSpan; -import android.view.View; - -/** - * Created by Gavriil Sitnikov on 14/11/2015. - * Span that is opening phone call intent. - */ -public class PhoneSpan extends URLSpan { - - public PhoneSpan(@NonNull final String phoneNumber) { - super(phoneNumber); - } - - @Override - public void onClick(@NonNull final View widget) { - super.onClick(widget); - try { - final Intent intent = new Intent(Intent.ACTION_DIAL); - intent.setData(Uri.parse(getURL())); - widget.getContext().startActivity(intent); - } catch (final ActivityNotFoundException exception) { - // Do nothing - } - } - - @Override - public void updateDrawState(@NonNull final TextPaint ds) { - super.updateDrawState(ds); - ds.setUnderlineText(false); - } - -} diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.kt new file mode 100644 index 0000000..a509516 --- /dev/null +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.kt @@ -0,0 +1,26 @@ +package ru.touchin.roboswag.components.utils.spans + +import android.content.ActivityNotFoundException +import android.content.Intent +import android.net.Uri +import android.view.View + +/** + * Created by Gavriil Sitnikov on 14/11/2015. + * Span that is opening phone call intent. + */ +class PhoneSpan(phoneNumber: String) : URLSpanWithoutUnderline(phoneNumber) { + + override fun onClick(widget: View) { + super.onClick(widget) + try { + val intent = Intent(Intent.ACTION_DIAL) + intent.data = Uri.parse(url) + widget.context.startActivity(intent) + } catch (exception: ActivityNotFoundException) { + // Do nothing + } + + } + +} diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt new file mode 100644 index 0000000..75f4d2e --- /dev/null +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt @@ -0,0 +1,40 @@ +package ru.touchin.roboswag.components.utils.spans + +import android.text.SpannableString +import android.text.Spanned +import android.text.style.URLSpan +import android.text.util.Linkify +import androidx.core.text.HtmlCompat + +/** + * Convert text with 'href' tags and raw links to spanned text with clickable URLSpan. + */ +fun String.getSpannedTextWithUrls( + removeUnderline: Boolean = true, + flags: Int = HtmlCompat.FROM_HTML_MODE_COMPACT +): Spanned { + + val spannableText = SpannableString(HtmlCompat.fromHtml(this, flags)) + + // Linkify removes all previous URLSpan's, we need to save all created spans for reapply after Linkify + val spans = spannableText.getUrlSpans() + Linkify.addLinks(spannableText, Linkify.WEB_URLS) + spans.forEach { + spannableText.setSpan(it.span, it.start, it.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + + if (!removeUnderline) { + spannableText.getUrlSpans() + .forEach { + spannableText.removeSpan(it.span) + spannableText.setSpan(URLSpanWithoutUnderline(it.span.url), it.start, it.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + + } + return spannableText +} + +private fun SpannableString.getUrlSpans() = getSpans(0, length, URLSpan::class.java) + .map { UrlSpanWithBorders(it, this.getSpanStart(it), this.getSpanEnd(it)) } + +private data class UrlSpanWithBorders(val span: URLSpan, val start: Int, val end: Int) diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/TypefaceSpan.java b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/TypefaceSpan.java deleted file mode 100644 index 690eaad..0000000 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/TypefaceSpan.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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. - * - */ - -package ru.touchin.roboswag.components.utils.spans; - -import android.graphics.Paint; -import android.graphics.Typeface; -import androidx.annotation.NonNull; -import android.text.TextPaint; -import android.text.style.MetricAffectingSpan; - -/** - * Created by Ilia Kurtov on 15/02/2016. - * Span for typefaces in texts. - * http://stackoverflow.com/a/15181195 - */ -public class TypefaceSpan extends MetricAffectingSpan { - - @NonNull - private final Typeface typeface; - - public TypefaceSpan(@NonNull final Typeface typeface) { - super(); - this.typeface = typeface; - } - - @Override - public void updateMeasureState(@NonNull final TextPaint textPaint) { - textPaint.setTypeface(typeface); - textPaint.setFlags(textPaint.getFlags() | Paint.SUBPIXEL_TEXT_FLAG); - } - - @Override - public void updateDrawState(@NonNull final TextPaint textPaint) { - textPaint.setTypeface(typeface); - textPaint.setFlags(textPaint.getFlags() | Paint.SUBPIXEL_TEXT_FLAG); - } - -} diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/TypefaceSpan.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/TypefaceSpan.kt new file mode 100644 index 0000000..44f5e2c --- /dev/null +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/TypefaceSpan.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) + * + * This file is part of RoboSwag library. + * + * 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. + * + */ + +package ru.touchin.roboswag.components.utils.spans + +import android.graphics.Paint +import android.graphics.Typeface +import android.text.TextPaint +import android.text.style.MetricAffectingSpan + +/** + * Created by Ilia Kurtov on 15/02/2016. + * Span for typefaces in texts. + * http://stackoverflow.com/a/15181195 + */ +class TypefaceSpan(private val typeface: Typeface) : MetricAffectingSpan() { + + override fun updateMeasureState(textPaint: TextPaint) { + textPaint.typeface = typeface + textPaint.flags = textPaint.flags or Paint.SUBPIXEL_TEXT_FLAG + } + + override fun updateDrawState(textPaint: TextPaint) { + textPaint.typeface = typeface + textPaint.flags = textPaint.flags or Paint.SUBPIXEL_TEXT_FLAG + } + +} diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/URLSpanWithoutUnderline.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/URLSpanWithoutUnderline.kt new file mode 100644 index 0000000..e37e18a --- /dev/null +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/URLSpanWithoutUnderline.kt @@ -0,0 +1,12 @@ +package ru.touchin.roboswag.components.utils.spans + +import android.text.TextPaint +import android.text.style.URLSpan + +open class URLSpanWithoutUnderline(url: String) : URLSpan(url) { + + override fun updateDrawState(ds: TextPaint) { + super.updateDrawState(ds) + ds.isUnderlineText = false + } +} diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java b/views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java index 36dbfcb..2592c8a 100644 --- a/views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java +++ b/views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java @@ -82,10 +82,10 @@ public class MaterialLoadingBar extends AppCompatImageView { final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MaterialLoadingBar, defStyleAttr, 0); - final int size = (int) typedArray.getDimension(R.styleable.MaterialLoadingBar_size, UiUtils.OfMetrics.dpToPixels(context, 48)); + final int size = (int) typedArray.getDimension(R.styleable.MaterialLoadingBar_size, UiUtils.OfMetrics.INSTANCE.dpToPixels(context, 48)); final int color = typedArray.getColor(R.styleable.MaterialLoadingBar_color, getPrimaryColor(context)); final float strokeWidth = typedArray.getDimension(R.styleable.MaterialLoadingBar_strokeWidth, - UiUtils.OfMetrics.dpToPixels(context, 4)); + UiUtils.OfMetrics.INSTANCE.dpToPixels(context, 4)); typedArray.recycle(); progressDrawable = new MaterialProgressDrawable(context, size); diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java b/views/src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java index daabf5c..667a757 100644 --- a/views/src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java +++ b/views/src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java @@ -69,7 +69,7 @@ public class MaterialProgressDrawable extends Drawable implements Runnable, Anim this.size = size; paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(UiUtils.OfMetrics.dpToPixels(context, DEFAULT_STROKE_WIDTH_DP)); + paint.setStrokeWidth(UiUtils.OfMetrics.INSTANCE.dpToPixels(context, DEFAULT_STROKE_WIDTH_DP)); paint.setColor(Color.BLACK); } diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java b/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java index 27b45e3..8bce61a 100644 --- a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java +++ b/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java @@ -326,8 +326,8 @@ public class TypefacedTextView extends AppCompatTextView { @SuppressLint("WrongCall") //WrongCall: actually this method is always calling from onMeasure private void computeScalableTextSize(final int maxWidth, final int maxHeight) { - final int minDifference = (int) UiUtils.OfMetrics.dpToPixels(getContext(), 1); - int difference = (int) UiUtils.OfMetrics.dpToPixels(getContext(), START_SCALABLE_DIFFERENCE); + final int minDifference = (int) UiUtils.OfMetrics.INSTANCE.dpToPixels(getContext(), 1); + int difference = (int) UiUtils.OfMetrics.INSTANCE.dpToPixels(getContext(), START_SCALABLE_DIFFERENCE); ScaleAction scaleAction = ScaleAction.DO_NOTHING; ScaleAction previousScaleAction = ScaleAction.DO_NOTHING; do { diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java b/views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java index 29307a8..abe7169 100644 --- a/views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java +++ b/views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java @@ -152,7 +152,7 @@ public final class AttributesUtils { */ @NonNull public static String viewError(@NonNull final View view, @NonNull final String errorText) { - return "Errors for view id=" + UiUtils.OfViews.getViewIdString(view) + ":\n" + errorText; + return "Errors for view id=" + UiUtils.OfViews.INSTANCE.getViewIdString(view) + ":\n" + errorText; } /** From 68113a8359f619e1b726bcb1d9661aa03a45ed48 Mon Sep 17 00:00:00 2001 From: Maxim Bachinsky Date: Thu, 7 Nov 2019 11:41:24 +0300 Subject: [PATCH 2/3] fix default arguments for old method --- .../main/java/ru/touchin/roboswag/components/utils/UiUtils.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt index 45339d2..4a89ea0 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt @@ -69,9 +69,9 @@ object UiUtils { */ @Deprecated( "use extension in SpanUtils", - ReplaceWith("text.getSpannedTextWithUrls()", "ru.touchin.roboswag.components.utils.spans.getSpannedTextWithUrls") + ReplaceWith("text.getSpannedTextWithUrls(removeUnderline = false)", "ru.touchin.roboswag.components.utils.spans.getSpannedTextWithUrls") ) - fun getSpannedTextWithUrls(text: String) = text.getSpannedTextWithUrls() + fun getSpannedTextWithUrls(text: String) = text.getSpannedTextWithUrls(removeUnderline = false) /** * Utilities methods related to metrics. From 7c38ec26ccb79598830999d5b1c3f4e7f57c91da Mon Sep 17 00:00:00 2001 From: Maxim Bachinsky Date: Thu, 7 Nov 2019 13:18:44 +0300 Subject: [PATCH 3/3] fix style comments from code review --- .../touchin/roboswag/components/utils/UiUtils.kt | 15 ++++++--------- .../roboswag/components/utils/spans/PhoneSpan.kt | 1 - .../roboswag/components/utils/spans/SpanUtils.kt | 12 ++++++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt index 4a89ea0..642520c 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt @@ -169,13 +169,13 @@ object UiUtils { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { val display = activity.windowManager.defaultDisplay - val realDisplayMetrics = DisplayMetrics() - display.getRealMetrics(realDisplayMetrics) + val realDisplayMetrics = DisplayMetrics().also(display::getRealMetrics) - val displayMetrics = DisplayMetrics() - display.getMetrics(displayMetrics) + val displayMetrics = DisplayMetrics().also(display::getMetrics) - return realDisplayMetrics.widthPixels - displayMetrics.widthPixels > 0 || realDisplayMetrics.heightPixels - displayMetrics.heightPixels > 0 + + return realDisplayMetrics.widthPixels - displayMetrics.widthPixels > 0 + || realDisplayMetrics.heightPixels - displayMetrics.heightPixels > 0 } val hasMenuKey = ViewConfiguration.get(activity).hasPermanentMenuKey() @@ -215,10 +215,7 @@ object UiUtils { * Hides device keyboard for target activity. */ fun hideSoftInput(activity: Activity) { - val focusedView = activity.currentFocus - if (focusedView != null) { - hideSoftInput(focusedView) - } + activity.currentFocus?.let(this::hideSoftInput) } /** diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.kt index a509516..45bae42 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.kt +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.kt @@ -20,7 +20,6 @@ class PhoneSpan(phoneNumber: String) : URLSpanWithoutUnderline(phoneNumber) { } catch (exception: ActivityNotFoundException) { // Do nothing } - } } diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt index 75f4d2e..5ad792f 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt @@ -25,11 +25,15 @@ fun String.getSpannedTextWithUrls( if (!removeUnderline) { spannableText.getUrlSpans() - .forEach { - spannableText.removeSpan(it.span) - spannableText.setSpan(URLSpanWithoutUnderline(it.span.url), it.start, it.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + .forEach { urlSpan -> + spannableText.removeSpan(urlSpan.span) + spannableText.setSpan( + URLSpanWithoutUnderline(urlSpan.span.url), + urlSpan.start, + urlSpan.end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) } - } return spannableText }