Merge branch 'master' into remove_buildscripts

This commit is contained in:
Maxim Bachinsky 2019-11-11 11:14:05 +03:00
commit 65a1076aee
15 changed files with 423 additions and 437 deletions

View File

@ -84,6 +84,7 @@ open class FragmentNavigation(
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param backStackName Name of [Fragment] in back stack;
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
* @param tag Optional tag name for the [Fragment];
*/
fun addToStack(
fragmentClass: Class<out Fragment>,
@ -92,7 +93,8 @@ open class FragmentNavigation(
addToStack: Boolean,
args: Bundle?,
backStackName: String?,
transactionSetup: ((FragmentTransaction) -> Unit)?
transactionSetup: ((FragmentTransaction) -> Unit)?,
tag: String?
) {
if (fragmentManager.isDestroyed) {
Lc.assertion("FragmentManager is destroyed")
@ -104,7 +106,7 @@ open class FragmentNavigation(
val fragmentTransaction = fragmentManager.beginTransaction()
transactionSetup?.invoke(fragmentTransaction)
fragmentTransaction.replace(containerViewId, fragment, null)
fragmentTransaction.replace(containerViewId, fragment, tag)
if (addToStack) {
fragmentTransaction
.addToBackStack(backStackName)
@ -145,15 +147,17 @@ open class FragmentNavigation(
* @param fragmentClass Class of [Fragment] to instantiate;
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
* @param tag Optional tag name for the [Fragment];
*/
fun push(
fragmentClass: Class<out Fragment>,
args: Bundle? = null,
addToStack: Boolean = true,
backStackName: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
transactionSetup: ((FragmentTransaction) -> Unit)? = null,
tag: String? = null
) {
addToStack(fragmentClass, null, 0, addToStack, args, backStackName, transactionSetup)
addToStack(fragmentClass, null, 0, addToStack, args, backStackName, transactionSetup, tag)
}
/**
@ -163,13 +167,15 @@ open class FragmentNavigation(
* @param targetFragment Target fragment to be set as [Fragment.getTargetFragment] of instantiated [Fragment];
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
* @param tag Optional tag name for the [Fragment];
*/
fun pushForResult(
fragmentClass: Class<out Fragment>,
targetFragment: Fragment,
targetRequestCode: Int,
args: Bundle? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
transactionSetup: ((FragmentTransaction) -> Unit)? = null,
tag: String? = null
) {
addToStack(
fragmentClass,
@ -178,7 +184,8 @@ open class FragmentNavigation(
true,
args,
null,
transactionSetup
transactionSetup,
tag
)
}
@ -189,14 +196,16 @@ open class FragmentNavigation(
* @param fragmentClass Class of [Fragment] to instantiate;
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
* @param tag Optional tag name for the [Fragment];
*/
fun setAsTop(
fragmentClass: Class<out Fragment>,
args: Bundle? = null,
addToStack: Boolean = true,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
transactionSetup: ((FragmentTransaction) -> Unit)? = null,
tag: String? = null
) {
addToStack(fragmentClass, null, 0, addToStack, args, TOP_FRAGMENT_TAG_MARK, transactionSetup)
addToStack(fragmentClass, null, 0, addToStack, args, TOP_FRAGMENT_TAG_MARK, transactionSetup, tag)
}
/**
@ -205,15 +214,17 @@ open class FragmentNavigation(
* @param fragmentClass Class of [Fragment] to instantiate;
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
* @param tag Optional tag name for the [Fragment];
*/
@JvmOverloads
fun setInitial(
fragmentClass: Class<out Fragment>,
args: Bundle? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
transactionSetup: ((FragmentTransaction) -> Unit)? = null,
tag: String? = null
) {
beforeSetInitialActions()
setAsTop(fragmentClass, args, false, transactionSetup)
setAsTop(fragmentClass, args, false, transactionSetup, tag)
}
/**

View File

@ -52,6 +52,7 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
* @param addToStack Flag to add this transaction to the back stack;
* @param backStackName Name of [Fragment] in back stack;
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;
* @param tag Optional tag name for the [Fragment];
* @param TState Type of state of fragment.
*/
fun <TState : Parcelable> pushViewController(
@ -59,7 +60,8 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
state: TState,
addToStack: Boolean = true,
backStackName: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
transactionSetup: ((FragmentTransaction) -> Unit)? = null,
tag: String? = null
) {
addToStack(
ViewControllerFragment::class.java,
@ -68,7 +70,8 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
addToStack,
ViewControllerFragment.args(viewControllerClass, state),
backStackName,
transactionSetup
transactionSetup,
tag
)
}
@ -81,6 +84,7 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
* @param state [Parcelable] of [ViewController]'s fragment;
* @param backStackName Name of [Fragment] in back stack;
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;
* @param tag Optional tag name for the [Fragment];
* @param TState Type of state of fragment;
* @param TTargetFragment Type of target fragment.
*/
@ -90,7 +94,8 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
targetFragment: TTargetFragment,
targetRequestCode: Int,
backStackName: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
transactionSetup: ((FragmentTransaction) -> Unit)? = null,
tag: String? = null
) {
addToStack(
ViewControllerFragment::class.java,
@ -99,7 +104,8 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
true,
ViewControllerFragment.args(viewControllerClass, state),
backStackName,
transactionSetup
transactionSetup,
tag
)
}
@ -110,13 +116,15 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
* @param viewControllerClass Class of [ViewController] to be pushed;
* @param state [Parcelable] of [ViewController]'s fragment;
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;
* @param tag Optional tag name for the [Fragment];
* @param TState Type of state of fragment.
*/
fun <TState : Parcelable> setViewControllerAsTop(
viewControllerClass: Class<out ViewController<out TActivity, TState>>,
state: TState,
addToStack: Boolean = true,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
transactionSetup: ((FragmentTransaction) -> Unit)? = null,
tag: String? = null
) {
addToStack(
ViewControllerFragment::class.java,
@ -125,7 +133,8 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
addToStack,
ViewControllerFragment.args(viewControllerClass, state),
TOP_FRAGMENT_TAG_MARK,
transactionSetup
transactionSetup,
tag
)
}
@ -136,15 +145,17 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
* @param viewControllerClass Class of [ViewController] to be pushed;
* @param state [Parcelable] of [ViewController]'s fragment;
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;
* @param tag Optional tag name for the [Fragment];
* @param TState Type of state of fragment.
*/
fun <TState : Parcelable> setInitialViewController(
viewControllerClass: Class<out ViewController<out TActivity, TState>>,
state: TState,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
transactionSetup: ((FragmentTransaction) -> Unit)? = null,
tag: String? = null
) {
beforeSetInitialActions()
setViewControllerAsTop(viewControllerClass, state, false, transactionSetup)
setViewControllerAsTop(viewControllerClass, state, false, transactionSetup, tag)
}
}

View File

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

View File

@ -0,0 +1,245 @@
/*
* 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(removeUnderline = false)", "ru.touchin.roboswag.components.utils.spans.getSpannedTextWithUrls")
)
fun getSpannedTextWithUrls(text: String) = text.getSpannedTextWithUrls(removeUnderline = false)
/**
* 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().also(display::getRealMetrics)
val displayMetrics = DisplayMetrics().also(display::getMetrics)
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) {
activity.currentFocus?.let(this::hideSoftInput)
}
/**
* 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)
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,25 @@
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
}
}
}

View File

@ -0,0 +1,44 @@
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 { urlSpan ->
spannableText.removeSpan(urlSpan.span)
spannableText.setSpan(
URLSpanWithoutUnderline(urlSpan.span.url),
urlSpan.start,
urlSpan.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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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