diff --git a/app/src/main/java/com/github/droidworksstudio/ktx/ActivityExtensions.kt b/app/src/main/java/com/github/droidworksstudio/ktx/ActivityExtensions.kt new file mode 100644 index 0000000..d9d54ac --- /dev/null +++ b/app/src/main/java/com/github/droidworksstudio/ktx/ActivityExtensions.kt @@ -0,0 +1,101 @@ +package com.github.droidworksstudio.ktx + +import android.app.Activity +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.res.Configuration +import android.net.Uri +import android.os.Build +import android.provider.Settings +import android.util.DisplayMetrics +import android.view.WindowManager +import android.widget.Toast +import androidx.annotation.RequiresApi +import androidx.core.content.getSystemService +import androidx.fragment.app.Fragment + +fun Activity.getDisplayWidth(): Int { + val windowManager = getSystemService() ?: return 0 + val width = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val windowMetrics = windowManager.currentWindowMetrics + val bounds = windowMetrics.bounds + bounds.width() + } else { + val displayMetrics = DisplayMetrics() + @Suppress("DEPRECATION") + windowManager.defaultDisplay.getMetrics(displayMetrics) + displayMetrics.widthPixels + } + return width +} + + +fun Activity.enablePortraitScreenOrientationForMobile(isPortrait: Boolean) { + if (!isTabletConfig()) { + requestedOrientation = if (isPortrait) ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + else ActivityInfo.SCREEN_ORIENTATION_SENSOR + } +} + +fun Activity.lockScreenOrientationChanges(lock: Boolean) { + requestedOrientation = + if (lock) ActivityInfo.SCREEN_ORIENTATION_LOCKED else ActivityInfo.SCREEN_ORIENTATION_SENSOR +} + +fun Activity.openEmailClient(emailTo: String, subject: String) { + val uriString = "mailto:$emailTo?subject=$subject" + val emailIntent = Intent(Intent.ACTION_SENDTO) + + emailIntent.data = Uri.parse(uriString) + + if (emailIntent.resolveActivity(packageManager) != null) { + startActivity(emailIntent) + } else { + startActivity(Intent.createChooser(emailIntent, null)) + } +} + +@RequiresApi(Build.VERSION_CODES.O) +fun Activity.openNotificationSettings() { + val intent = Intent() + intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS + intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName) + intent.putExtra("app_package", packageName) + intent.putExtra("app_uid", applicationInfo.uid) + startActivity(intent) +} + +/** + * Starting from appcompat v.1.1.0 system overrides baseContext after [attachBaseContext] + * causing resetting locale settings. To prevent this we must call [applyOverrideConfiguration] + * with modified [overrideConfiguration] object. + * + * That's a workaround until this behaviour will be fixed in future appcompat releases. + */ +fun Activity.setupOverrideConfiguration(overrideConfiguration: Configuration?): Configuration? { + if (overrideConfiguration != null) { + val uiMode = overrideConfiguration.uiMode + overrideConfiguration.setTo(baseContext.resources.configuration) + overrideConfiguration.uiMode = uiMode + } + + return overrideConfiguration +} + +/** + * Navigates to the current Google user's account + * subscriptions in Google Play app. + */ +fun Activity.openGooglePlaySubscriptions() { + val uriString = "https://play.google.com/store/account/subscriptions" + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uriString)) + startActivity(intent) +} + +fun Activity.showLongToast(message: String) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show() +} + +fun Activity.showShortToast(message: String) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show() +} \ No newline at end of file diff --git a/app/src/main/java/com/github/droidworksstudio/ktx/ContextExtensions.kt b/app/src/main/java/com/github/droidworksstudio/ktx/ContextExtensions.kt new file mode 100644 index 0000000..37b0004 --- /dev/null +++ b/app/src/main/java/com/github/droidworksstudio/ktx/ContextExtensions.kt @@ -0,0 +1,130 @@ +package com.github.droidworksstudio.ktx + +import android.app.SearchManager +import android.content.Context +import android.content.Intent +import android.content.pm.LauncherApps +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.drawable.AdaptiveIconDrawable +import android.net.Uri +import android.os.Build +import android.os.UserHandle +import android.view.ContextThemeWrapper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.IconCompat +import androidx.core.graphics.drawable.toBitmap +import androidx.core.os.ConfigurationCompat +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleOwner + +fun Context.isTabletConfig(): Boolean = + resources.configuration.smallestScreenWidthDp >= SMALLEST_WIDTH_600 + +fun Context.isPortraitSw600Config(): Boolean = + resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT && + resources.configuration.smallestScreenWidthDp >= SMALLEST_WIDTH_600 + +fun Context.isLandscapeSw600Config(): Boolean = + resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && + resources.configuration.smallestScreenWidthDp >= SMALLEST_WIDTH_600 + +fun Context.isLandscapeDisplayOrientation(): Boolean = + resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + +internal fun Context.addLifecycleObserver(observer: LifecycleObserver) { + when (this) { + is LifecycleOwner -> this.lifecycle.addObserver(observer) + is ContextThemeWrapper -> this.baseContext.addLifecycleObserver(observer) + is androidx.appcompat.view.ContextThemeWrapper -> this.baseContext.addLifecycleObserver( + observer + ) + } +} + +fun Context.getMiddleScreenX(): Int { + val screenEndX = this.resources.displayMetrics.widthPixels + return (screenEndX / 2) +} + +fun Context.getMiddleScreenY(): Int { + val screenEndY = this.resources.displayMetrics.heightPixels + return (screenEndY / 2) +} + +const val SMALLEST_WIDTH_600: Int = 600 +fun Context.createIconWithResourceCompat( + @DrawableRes vectorIconId: Int, + @DrawableRes adaptiveIconForegroundId: Int, + @DrawableRes adaptiveIconBackgroundId: Int +): IconCompat { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val adaptiveIconDrawable = AdaptiveIconDrawable( + ContextCompat.getDrawable(this, adaptiveIconBackgroundId), + ContextCompat.getDrawable(this, adaptiveIconForegroundId) + ) + + IconCompat.createWithAdaptiveBitmap( + adaptiveIconDrawable.toBitmap( + config = Bitmap.Config.ARGB_8888 + ) + ) + } else { + IconCompat.createWithResource(this, vectorIconId) + } +} + +fun Context.currentLanguage() = ConfigurationCompat.getLocales(resources.configuration)[0]?.language + +fun Context.openBrowser(url: String, clearFromRecent: Boolean = true) { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + browserIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + if (clearFromRecent) browserIntent.flags = + browserIntent.flags or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + startActivity(browserIntent) +} + +fun Context.inflate(resource: Int, root: ViewGroup? = null, attachToRoot: Boolean = false): View { + return LayoutInflater.from(this).inflate(resource, root, attachToRoot) +} + +fun Context.getColorCompat(@ColorRes color: Int) = ContextCompat.getColor(this, color) + +fun Context.getDrawableCompat(@DrawableRes drawable: Int) = + ContextCompat.getDrawable(this, drawable) + +fun Context.showLongToast(message: String) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show() +} + +fun Context.showShortToast(message: String) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show() +} + +fun Context.openSearch(query: String? = null) { + val intent = Intent(Intent.ACTION_WEB_SEARCH) + intent.putExtra(SearchManager.QUERY, query ?: "") + startActivity(intent) +} + +fun Context.openUrl(url: String) { + if (url.isEmpty()) return + val intent = Intent(Intent.ACTION_VIEW) + intent.data = Uri.parse(url) + startActivity(intent) +} + +fun Context.isPackageInstalled( + packageName: String, + userHandle: UserHandle = android.os.Process.myUserHandle() +): Boolean { + val launcher = getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps + val activityInfo = launcher.getActivityList(packageName, userHandle) + return activityInfo.size > 0 +} \ No newline at end of file diff --git a/app/src/main/java/com/github/droidworksstudio/ktx/FragmentExtensions.kt b/app/src/main/java/com/github/droidworksstudio/ktx/FragmentExtensions.kt new file mode 100644 index 0000000..dca99bc --- /dev/null +++ b/app/src/main/java/com/github/droidworksstudio/ktx/FragmentExtensions.kt @@ -0,0 +1,41 @@ +package com.github.droidworksstudio.ktx + +import android.content.Context +import android.content.Intent +import android.view.inputmethod.InputMethodManager +import android.widget.Toast +import androidx.fragment.app.Fragment + +fun Fragment.showKeyboard() { + val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + view?.let { v -> + v.requestFocus() + imm?.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT) + } +} + +fun Fragment.hideKeyboard() { + val view = view?.findFocus() ?: return + val imm: InputMethodManager? = + view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? + imm?.hideSoftInputFromWindow(view.windowToken, 0) + view.clearFocus() +} + +fun Fragment.showLongToast(message: String) { + Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show() +} + +fun Fragment.showShortToast(message: String) { + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() +} + +fun Fragment.restartApp() { + val packageManager = requireContext().packageManager + val intent = packageManager.getLaunchIntentForPackage(requireContext().packageName) + intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + if (intent != null) { + startActivity(intent) + } + requireActivity().finish() +} \ No newline at end of file diff --git a/app/src/main/java/com/github/droidworksstudio/ktx/MetricsExtensions.kt b/app/src/main/java/com/github/droidworksstudio/ktx/MetricsExtensions.kt new file mode 100644 index 0000000..ed8d845 --- /dev/null +++ b/app/src/main/java/com/github/droidworksstudio/ktx/MetricsExtensions.kt @@ -0,0 +1,53 @@ +package com.github.droidworksstudio.ktx + +import android.content.res.Resources +import android.util.TypedValue + +/** + * Converts value in pixels (px) into value in device-independent pixels (dp) + */ +fun Int.pxToDp(): Int { + val metrics = Resources.getSystem().displayMetrics + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, this.toFloat(), metrics).toInt() +} + +/** + * Converts value in pixels (px) into value in scaled pixels (sp) + */ +fun Int.pxToSp(): Int { + val metrics = Resources.getSystem().displayMetrics + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, this.toFloat(), metrics).toInt() +} + +/** + * Converts value in device-independent pixels (dp) into value in pixels (px) + */ +fun Int.dpToPx(): Int { + val metrics = Resources.getSystem().displayMetrics + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), metrics).toInt() +} + +/** + * Converts value in density-independent pixels (dp) into value in scaled pixels (sp) + */ +fun Int.dpToSp(): Int { + val metrics = Resources.getSystem().displayMetrics + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), metrics).toInt() +} + +/** + * Converts value in scaled pixels (sp) into value in device-independent pixels (dp) + */ +fun Int.spToDp(): Int { + val metrics = Resources.getSystem().displayMetrics + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this.toFloat(), metrics).toInt() +} + +/** + * Converts value in scaled pixels (sp) into value in pixels (px) + */ +fun Int.spToPx(): Int { + val metrics = Resources.getSystem().displayMetrics + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this.toFloat(), metrics).toInt() +} + diff --git a/app/src/main/java/com/github/droidworksstudio/ktx/StringExtensions.kt b/app/src/main/java/com/github/droidworksstudio/ktx/StringExtensions.kt new file mode 100644 index 0000000..a51bffc --- /dev/null +++ b/app/src/main/java/com/github/droidworksstudio/ktx/StringExtensions.kt @@ -0,0 +1,104 @@ +package com.github.droidworksstudio.ktx + +import android.content.Context +import android.graphics.Color +import android.graphics.Typeface +import android.text.Html +import android.text.Spannable +import android.text.SpannableString +import android.text.TextPaint +import android.text.style.ClickableSpan +import android.text.style.ForegroundColorSpan +import android.text.style.StyleSpan +import android.view.View +import android.widget.Toast +import androidx.core.text.getSpans + +/** + * Returns [Spannable] where the term is + * highlighted as a bold text. + */ +fun String.highlightTerm(term: String): SpannableString { + val regex = Regex(term.lowercase()) + val matches = regex.findAll(this.lowercase()) + val ranges = matches.map { it.range } + val spannable = SpannableString(this) + ranges.forEach { + spannable.setSpan( + ForegroundColorSpan(Color.BLACK), + it.first, + it.last + 1, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + + return spannable +} + +/** + * Returns [Spannable] where all (e.g. italic) tags are replaced + * with (e.g. bold) + * + * Important: works only with tags replacing it only with tags. + * Using deprecated [Html.fromHtml] as it's deprecated only from API 24. + */ +fun String.replaceItalicWithBold(): SpannableString { + val spannable = SpannableString(Html.fromHtml(this, Html.FROM_HTML_MODE_LEGACY)) + + spannable.getSpans(0, spannable.length) + .replaceItalicInSpannable(spannable) + + return spannable +} + +fun Array.replaceItalicInSpannable(spannable: SpannableString) = forEach { + if (it.style == Typeface.ITALIC) { + spannable.setSpan( + StyleSpan(Typeface.BOLD), + spannable.getSpanStart(it), + spannable.getSpanEnd(it), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + spannable.setSpan( + ForegroundColorSpan(Color.BLACK), + spannable.getSpanStart(it), + spannable.getSpanEnd(it), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + spannable.removeSpan(it) + } +} + +fun CharSequence.makeTextClickable( + clickableText: String, + clickableTextColor: Int, + clickListener: () -> (Unit) +): SpannableString { + val regex = Regex(clickableText) + val matches = regex.findAll(this) + val ranges = matches.map { it.range } + val spannable = SpannableString(this) + val clickableSpan = object : ClickableSpan() { + override fun onClick(widget: View) { + clickListener.invoke() + } + + override fun updateDrawState(ds: TextPaint) { + ds.color = clickableTextColor + ds.isUnderlineText = false + } + } + ranges.forEach { + spannable.setSpan(clickableSpan, it.first, it.last + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + + return spannable +} + +fun String.showLongToast(context: Context) { + Toast.makeText(context, this, Toast.LENGTH_LONG).show() +} + +fun String.showShortToast(context: Context) { + Toast.makeText(context, this, Toast.LENGTH_SHORT).show() +} \ No newline at end of file diff --git a/app/src/main/java/com/github/droidworksstudio/ktx/ViewExtensions.kt b/app/src/main/java/com/github/droidworksstudio/ktx/ViewExtensions.kt new file mode 100644 index 0000000..e94d96a --- /dev/null +++ b/app/src/main/java/com/github/droidworksstudio/ktx/ViewExtensions.kt @@ -0,0 +1,375 @@ +package com.github.droidworksstudio.ktx + +import android.annotation.SuppressLint +import android.content.Context +import android.text.Editable +import android.text.TextWatcher +import android.text.method.LinkMovementMethod +import android.view.View +import android.view.ViewGroup +import android.view.Window +import android.view.WindowManager +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager +import android.widget.AutoCompleteTextView +import android.widget.CheckBox +import android.widget.EditText +import android.widget.TextView +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.text.toSpannable +import androidx.core.view.ViewCompat +import androidx.core.view.children +import androidx.core.view.doOnPreDraw +import androidx.core.widget.TextViewCompat +import androidx.lifecycle.MutableLiveData +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.snackbar.Snackbar + +fun EditText.value() = text.toString() + +inline fun EditText.setOnEditorActionListener(crossinline onAction: (Int) -> Boolean) { + setOnEditorActionListener { _, actionId, _ -> onAction(actionId) } +} + +inline fun EditText.setOnDoneEditorActionListener(crossinline onAction: (Int) -> Unit = {}) { + setOnEditorActionListener { actionId -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + onAction(actionId) + true + } else false + } +} + +/** + * Runs [onAction] block and hides a keyboard when [EditorInfo.IME_ACTION_DONE] is fired. + * + * @param onAction function to be called when [[EditorInfo.IME_ACTION_DONE] is fired + */ +inline fun EditText.setHideKeyboardEditorActionListener(crossinline onAction: (Int) -> Unit = {}) { + setOnDoneEditorActionListener { + hideKeyboard() + onAction(it) + } +} + +fun View.showKeyboard() { + val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + this.let { v -> + v.requestFocus() + imm?.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT) + } +} + +fun View.hideKeyboard() { + val imm: InputMethodManager? = + context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? + imm?.hideSoftInputFromWindow(windowToken, 0) + this.clearFocus() +} + + +/** + * @return [View.VISIBLE] if Boolean value is true, [View.GONE] otherwise. + */ +fun Boolean.asVisibleOrGoneFlag() = if (this) View.VISIBLE else View.GONE + +/** + * @return [View.GONE] if Boolean value is true, [View.VISIBLE] otherwise. + */ +fun Boolean.asGoneOrVisibleFlag() = if (this) View.GONE else View.VISIBLE + +/** + * @return [View.VISIBLE] if Boolean value is true, [View.INVISIBLE] otherwise. + */ +fun Boolean.asVisibleOrInvisibleFlag() = if (this) View.VISIBLE else View.INVISIBLE + +/** + * Resets nested vertical scroll position. + * + * Can be used to reset [CoordinatorLayout]'s behaviours that depends on scrolling views. + */ +fun RecyclerView.resetNestedVerticalScroll() { + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) + dispatchNestedPreScroll(0, -Integer.MAX_VALUE, null, null) + dispatchNestedScroll(0, -Integer.MAX_VALUE, 0, 0, null) + stopNestedScroll() +} + +@SuppressLint("SwitchIntDef") +fun LinearLayoutManager.getCurrentPosition(midScreenX: Int, midScreenY: Int): Int = + when (this.orientation) { + LinearLayoutManager.HORIZONTAL -> getCurrentPositionForHorizontalOrientation(midScreenX) + LinearLayoutManager.VERTICAL -> getCurrentPositionForVerticalOrientation(midScreenY) + else -> RecyclerView.NO_POSITION + } + +/** + * Get current position for [LinearLayoutManager.HORIZONTAL] + * + * @return position of center item if it exists else + * @return first completely visible or first visible item position + * + * @return [RecyclerView.NO_POSITION] if [LinearLayoutManager] doesn't have horizontal orientation + */ +private fun LinearLayoutManager.getCurrentPositionForHorizontalOrientation(midScreenX: Int): Int { + if (this.orientation == LinearLayoutManager.HORIZONTAL) { + val centerPosition = getCenterPositionForHorizontalOrientation(midScreenX) + return if (centerPosition == RecyclerView.NO_POSITION) { + getCompletelyVisibleOrFirstPosition() + } else centerPosition + } + + return RecyclerView.NO_POSITION +} + +private fun LinearLayoutManager.getCompletelyVisibleOrFirstPosition(): Int { + val firstCompletelyVisiblePosition = findFirstCompletelyVisibleItemPosition() + + return if (firstCompletelyVisiblePosition == RecyclerView.NO_POSITION) { + findFirstVisibleItemPosition() + } else { + firstCompletelyVisiblePosition + } +} + +/** + * Get position of center item for [LinearLayoutManager.HORIZONTAL] + * + * @return position of item with start X less than middle of the screen and end X more than middle of the screen + * + * @return [RecyclerView.NO_POSITION] if [LinearLayoutManager] doesn't have horizontal orientation or there is no item that meets requirements + */ +private fun LinearLayoutManager.getCenterPositionForHorizontalOrientation(midScreenX: Int): Int { + if (this.orientation == LinearLayoutManager.HORIZONTAL) { + val firstPosition = this.findFirstVisibleItemPosition() + val lastPosition = this.findLastVisibleItemPosition() + + for (position in firstPosition..lastPosition) { + val view = this.findViewByPosition(position) + if (view != null) { + val viewWidth = view.measuredWidth + val viewStartX = view.x + val viewEndX = viewStartX + viewWidth + if (viewStartX < midScreenX && viewEndX > midScreenX) { + return position + } + } + } + + return RecyclerView.NO_POSITION + } + + return RecyclerView.NO_POSITION +} + +/** + * Get current position for [LinearLayoutManager.VERTICAL] + * + * @return position of center item if it exists else + * @return first completely visible or first visible item position + * + * @return [RecyclerView.NO_POSITION] if [LinearLayoutManager] doesn't have vertical orientation + */ + +private fun LinearLayoutManager.getCurrentPositionForVerticalOrientation(midScreenY: Int): Int { + if (this.orientation == LinearLayoutManager.VERTICAL) { + val centerPosition = getCenterPositionForVerticalOrientation(midScreenY) + return if (centerPosition == RecyclerView.NO_POSITION) { + getCompletelyVisibleOrFirstPosition() + } else centerPosition + } + + return RecyclerView.NO_POSITION +} + +/** + * Get position of center item for [LinearLayoutManager.VERTICAL] + * + * @return position of item with start Y less than middle of the screen and end Y more than middle of the screen + * + * @return [RecyclerView.NO_POSITION] if [LinearLayoutManager] doesn't have vertical orientation or there is no item that meets requirements + */ +private fun LinearLayoutManager.getCenterPositionForVerticalOrientation(midScreenY: Int): Int { + if (this.orientation == LinearLayoutManager.VERTICAL) { + val firstPosition = this.findFirstVisibleItemPosition() + val lastPosition = this.findLastVisibleItemPosition() + + for (position in firstPosition..lastPosition) { + val view = this.findViewByPosition(position) + if (view != null) { + val viewHeight = view.measuredHeight + val viewStartY = view.y + val viewEndY = viewStartY + viewHeight + if (viewStartY < midScreenY && viewEndY > midScreenY) { + return position + } + } + } + + return RecyclerView.NO_POSITION + } + + return RecyclerView.NO_POSITION +} + +fun View.setBottomPadding(bottomPadding: Int) { + setPadding(paddingLeft, paddingTop, paddingRight, bottomPadding) +} + +/** + * Triggers when recycler view state changes to [RecyclerView.SCROLL_STATE_IDLE]. + */ +inline fun RecyclerView.addOnIdleStateListener(crossinline listener: (RecyclerView) -> Unit) { + addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + if (newState == RecyclerView.SCROLL_STATE_IDLE) { + listener.invoke(recyclerView) + } + } + }) +} + +fun View.setProportionalHeight(imageWidth: Float, imageHeight: Float) { + doOnPreDraw { + val maxWidth = it.width.toFloat() + it.layoutParams = + it.layoutParams.calculateProportionalHeight(maxWidth, imageWidth, imageHeight) + } +} + +fun View.setProportionalAspectRatio(imageWidth: Float, imageHeight: Float) { + doOnPreDraw { + val maxWidth = it.width.toFloat() + val maxHeight = it.height.toFloat() + it.layoutParams = + it.layoutParams.calculateAspectRatio(maxWidth, maxHeight, imageWidth, imageHeight) + } +} + +fun View.setElevationCompat(value: Float) { + ViewCompat.setElevation(this, value) +} + +fun View.getElevationCompat() = ViewCompat.getElevation(this) + +fun View.enableChildrenViews(enable: Boolean) { + this.isEnabled = enable + if (this is ViewGroup) { + this.children.forEach { it.enableChildrenViews(enable) } + } +} + +/** + * Attaches Live data to Edit Text. + * This will update Live Data data with all text changes from Edit Text. + */ +fun AutoCompleteTextView.attachLiveData(data: MutableLiveData) { + setOnItemClickListener { parent, _, position, _ -> + @Suppress("UNCHECKED_CAST") + data.value = parent.adapter.getItem(position) as T + } +} + + +/** + * Attaches Live data to Edit Text. + * This will update Live Data data with all text changes from Edit Text. + */ +fun EditText.attachLiveData(data: MutableLiveData) { + data.value = text.toString() + addTextChangedListener { + data.value = it.toString() + } +} + +/** + * Attaches Live data to Check Box. + * This will update Live Data data with all state changes from Check Box. + */ +fun CheckBox.attachLiveData(data: MutableLiveData) { + data.value = isChecked + setOnCheckedChangeListener { _, isChecked -> + data.value = isChecked + } +} + +inline fun EditText.addTextChangedListener(crossinline onTextChanged: (text: CharSequence?) -> Unit) { + addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(p0: Editable?) { + } + + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + onTextChanged.invoke(p0) + } + }) +} + +fun TextView.setTextAppearanceCompat(appearanceId: Int) { + TextViewCompat.setTextAppearance(this, appearanceId) +} + +fun Window.disableScreenshots() { + this.setFlags( + WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE + ) +} + +fun Snackbar.centerMessage(): Snackbar = apply { + val textView = + this.view.findViewById(com.google.android.material.R.id.snackbar_text) + textView.textAlignment = View.TEXT_ALIGNMENT_CENTER +} + +fun Snackbar.show(isCentered: Boolean) { + if (isCentered) { + this.centerMessage() + } + + this.show() +} + +/** + * Searches for [clickableText] inside the TextView's text property. + * If found, highlights [clickableText] with [clickableTextColor] and sets [clickListener] to it. + * + * @param clickableText text that must be present inside TextView's text property + * @param clickableTextColor color for highlighting [clickableText] + * @param clickListener functions that will we invoked on [clickableText] click + */ +fun TextView.makeTextClickable( + clickableText: String, + clickableTextColor: Int, + clickListener: () -> (Unit) +) { + this.movementMethod = LinkMovementMethod.getInstance() + this.text = + this.text.toString().makeTextClickable(clickableText, clickableTextColor, clickListener) +} + +fun TextView.makeSpannableTextClickable( + clickableText: String, + clickableTextColor: Int, + clickListener: () -> (Unit) +) { + this.movementMethod = LinkMovementMethod.getInstance() + this.text = + this.text.toSpannable().makeTextClickable(clickableText, clickableTextColor, clickListener) +} + +fun View.removeFocus() { + clearFocus() + isFocusable = false + isFocusableInTouchMode = false +} + +fun View.setFocus() { + isFocusable = true + isFocusableInTouchMode = true +} + +fun Boolean.asTextOrNullInputType() = if (this) EditorInfo.TYPE_CLASS_TEXT else EditorInfo.TYPE_NULL \ No newline at end of file diff --git a/app/src/main/java/com/github/droidworksstudio/ktx/ViewGroupExtensions.kt b/app/src/main/java/com/github/droidworksstudio/ktx/ViewGroupExtensions.kt new file mode 100644 index 0000000..5ce6452 --- /dev/null +++ b/app/src/main/java/com/github/droidworksstudio/ktx/ViewGroupExtensions.kt @@ -0,0 +1,61 @@ +package com.github.droidworksstudio.ktx + +import android.annotation.SuppressLint +import android.view.ViewGroup +import android.widget.RadioButton +import android.widget.RadioGroup +import androidx.appcompat.view.SupportMenuInflater +import androidx.appcompat.widget.ActionMenuView +import androidx.lifecycle.MutableLiveData +import kotlin.math.min + +fun ViewGroup.LayoutParams.calculateAspectRatio( + maxWidth: Float, + maxHeight: Float, + imageWidth: Float, + imageHeight: Float +): ViewGroup.LayoutParams = apply { + val widthRatio = maxWidth / imageWidth + val heightRatio = maxHeight / imageHeight + val bestRatio = min(widthRatio, heightRatio) + + this.width = (imageWidth * bestRatio).toInt() + this.height = (imageHeight * bestRatio).toInt() +} + +fun ViewGroup.LayoutParams.calculateProportionalHeight( + maxWidth: Float, + imageWidth: Float, + imageHeight: Float +): ViewGroup.LayoutParams = apply { + val aspectRatio = maxWidth / imageWidth + this.height = (imageHeight * aspectRatio).toInt() +} + +/** + * Attaches Live data to Radio Group. + * This will update Live Data data with selected radio button text. + */ +fun RadioGroup.attachLiveDataForValue(data: MutableLiveData) { + data.value = getCheckedView()?.text.toString() + setOnCheckedChangeListener { group, checkedId -> + val checkedView = group.findViewById(checkedId) + data.value = checkedView.text.toString() + } +} + +/** + * Attaches Live data to Radio Group. + * This will update Live Data data with selected radio button text. + */ +fun RadioGroup.attachLiveDataForId(data: MutableLiveData) { + data.value = checkedRadioButtonId + setOnCheckedChangeListener { _, checkedId -> + data.value = checkedId + } +} + +fun RadioGroup.getCheckedView() = findViewById(checkedRadioButtonId) ?: null + +@SuppressLint("RestrictedApi") +fun ActionMenuView.inflateMenu(menuId: Int) = SupportMenuInflater(context).inflate(menuId, menu) \ No newline at end of file diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppExtensions.kt b/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppExtensions.kt index aae6097..27b8843 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppExtensions.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppExtensions.kt @@ -1,53 +1,15 @@ package com.github.droidworksstudio.launcher.helper -import android.app.SearchManager import android.content.Context import android.content.Intent import android.content.SharedPreferences -import android.content.pm.LauncherApps import android.net.Uri -import android.os.UserHandle import android.util.Log -import android.view.View -import android.view.inputmethod.InputMethodManager -import android.widget.EditText -import androidx.fragment.app.Fragment +import com.github.droidworksstudio.ktx.openUrl import com.github.droidworksstudio.launcher.Constants import java.io.File import java.io.IOException -fun View.hideKeyboard() { - this.clearFocus() - val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(windowToken, 0) -} - -fun View.showKeyboard(show: Boolean = true) { - if (show.not()) return - if (this.requestFocus()) - this.postDelayed({ - this.findViewById(androidx.appcompat.R.id.search_src_text).apply { - isCursorVisible = false - } - val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - @Suppress("DEPRECATION") - imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY) - }, 100) -} - -fun Context.openSearch(query: String? = null) { - val intent = Intent(Intent.ACTION_WEB_SEARCH) - intent.putExtra(SearchManager.QUERY, query ?: "") - startActivity(intent) -} - -fun Context.openUrl(url: String) { - if (url.isEmpty()) return - val intent = Intent(Intent.ACTION_VIEW) - intent.data = Uri.parse(url) - startActivity(intent) -} - fun Context.searchOnPlayStore(query: String? = null): Boolean { return try { val playStoreIntent = Intent(Intent.ACTION_VIEW) @@ -78,7 +40,8 @@ fun Context.searchCustomSearchEngine(searchQuery: String? = null): Boolean { } fun Context.backupSharedPreferences(backupFileName: String) { - val sharedPreferences: SharedPreferences = this.getSharedPreferences(Constants.PREFS_FILENAME, 0) + val sharedPreferences: SharedPreferences = + this.getSharedPreferences(Constants.PREFS_FILENAME, 0) val allPrefs = sharedPreferences.all val backupFile = File(filesDir, backupFileName) @@ -116,7 +79,8 @@ fun Context.backupSharedPreferences(backupFileName: String) { } fun Context.restoreSharedPreferences(backupFileName: String) { - val sharedPreferences: SharedPreferences = this.getSharedPreferences(Constants.PREFS_FILENAME, 0) + val sharedPreferences: SharedPreferences = + this.getSharedPreferences(Constants.PREFS_FILENAME, 0) val editor = sharedPreferences.edit() val backupFile = File(filesDir, backupFileName) @@ -127,7 +91,11 @@ fun Context.restoreSharedPreferences(backupFileName: String) { backupFile.forEachLine { line -> val (key, value) = line.split("=", limit = 2) when { - value.toBooleanStrictOrNull() != null -> editor.putBoolean(key, value.toBoolean()) + value.toBooleanStrictOrNull() != null -> editor.putBoolean( + key, + value.toBoolean() + ) + value.toIntOrNull() != null -> editor.putInt(key, value.toInt()) value.toFloatOrNull() != null -> editor.putFloat(key, value.toFloat()) value.toLongOrNull() != null -> editor.putLong(key, value.toLong()) @@ -145,20 +113,4 @@ fun Context.restoreSharedPreferences(backupFileName: String) { } else { println("Backup file does not exist.") } -} - -fun Fragment.restartApp() { - val packageManager = requireContext().packageManager - val intent = packageManager.getLaunchIntentForPackage(requireContext().packageName) - intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - if (intent != null) { - startActivity(intent) - } - requireActivity().finish() -} - -fun Context.isPackageInstalled(packageName: String, userHandle: UserHandle = android.os.Process.myUserHandle()): Boolean { - val launcher = getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps - val activityInfo = launcher.getActivityList(packageName, userHandle) - return activityInfo.size > 0 } \ No newline at end of file diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt b/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt index 0516837..bdeb49e 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt @@ -24,6 +24,7 @@ import android.view.WindowManager import android.widget.TextView import android.widget.Toast import androidx.appcompat.widget.LinearLayoutCompat +import com.github.droidworksstudio.ktx.showLongToast import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.github.droidworksstudio.launcher.Constants import com.github.droidworksstudio.launcher.R @@ -163,7 +164,7 @@ class AppHelper @Inject constructor() { if (intent != null) { context.startActivity(intent) } else { - showToast(context, "Failed to open the application") + context.showLongToast("Failed to open the application") } } @@ -208,7 +209,7 @@ class AppHelper @Inject constructor() { } catch (e: ActivityNotFoundException) { // Digital Wellbeing app is not installed or cannot be opened // Handle this case as needed - showToast(context, "Digital Wellbeing is not available on this device.") + context.showLongToast("Digital Wellbeing is not available on this device.") } } @@ -220,7 +221,7 @@ class AppHelper @Inject constructor() { } catch (e: ActivityNotFoundException) { // Battery manager settings cannot be opened // Handle this case as needed - showToast(context, "Battery manager settings are not available on this device.") + context.showLongToast("Battery manager settings are not available on this device.") } } @@ -256,11 +257,6 @@ class AppHelper @Inject constructor() { } } - - fun showToast(context: Context, message: String) { - Toast.makeText(context, message, Toast.LENGTH_SHORT).show() - } - fun showStatusBar(window: Window) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { window.insetsController?.show(WindowInsets.Type.statusBars()) diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/helper/FingerprintHelper.kt b/app/src/main/java/com/github/droidworksstudio/launcher/helper/FingerprintHelper.kt index a4023d4..9aa41a4 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/helper/FingerprintHelper.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/helper/FingerprintHelper.kt @@ -5,6 +5,7 @@ import androidx.biometric.BiometricPrompt import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController +import com.github.droidworksstudio.ktx.showLongToast import com.github.droidworksstudio.launcher.R import com.github.droidworksstudio.launcher.data.entities.AppInfo import javax.inject.Inject @@ -66,21 +67,16 @@ class FingerprintHelper @Inject constructor(private val fragment: Fragment) { } override fun onAuthenticationFailed() { - appHelper.showToast( - fragment.requireContext(), - fragment.getString(R.string.authentication_failed) - ) + fragment.requireContext() + .showLongToast(fragment.getString(R.string.authentication_failed)) } override fun onAuthenticationError(errorCode: Int, errorMessage: CharSequence) { when (errorCode) { - BiometricPrompt.ERROR_USER_CANCELED -> appHelper.showToast( - fragment.requireContext(), - fragment.getString(R.string.authentication_cancel) - ) + BiometricPrompt.ERROR_USER_CANCELED -> fragment.requireContext() + .showLongToast(fragment.getString(R.string.authentication_cancel)) - else -> appHelper.showToast( - fragment.requireContext(), + else -> fragment.requireContext().showLongToast( fragment.getString(R.string.authentication_error) .format(errorMessage, errorCode) ) @@ -111,10 +107,8 @@ class FingerprintHelper @Inject constructor(private val fragment: Fragment) { BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE, BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> sendToTarget(runNavigation) - else -> appHelper.showToast( - fragment.requireContext(), - fragment.getString(R.string.authentication_failed) - ) + else -> fragment.requireContext() + .showLongToast(fragment.getString(R.string.authentication_failed)) } } @@ -122,10 +116,8 @@ class FingerprintHelper @Inject constructor(private val fragment: Fragment) { try { fragment.findNavController().navigate(runNavigation) } catch (e: Exception) { - appHelper.showToast( - fragment.requireContext(), - fragment.getString(R.string.authentication_failed) - ) + fragment.requireContext() + .showLongToast(fragment.getString(R.string.authentication_failed)) } } } diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/listener/DeviceAdmin.kt b/app/src/main/java/com/github/droidworksstudio/launcher/listener/DeviceAdmin.kt index b58bc96..ad4ff17 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/listener/DeviceAdmin.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/listener/DeviceAdmin.kt @@ -4,15 +4,16 @@ import android.app.admin.DeviceAdminReceiver import android.content.Context import android.content.Intent import android.widget.Toast +import com.github.droidworksstudio.ktx.showLongToast class DeviceAdmin : DeviceAdminReceiver() { override fun onEnabled(context: Context, intent: Intent) { super.onEnabled(context, intent) - Toast.makeText(context, "Enabled", Toast.LENGTH_SHORT).show() + context.showLongToast("Enabled") } override fun onDisabled(context: Context, intent: Intent) { super.onDisabled(context, intent) - Toast.makeText(context, "Disabled", Toast.LENGTH_SHORT).show() + context.showLongToast("Disabled") } } \ No newline at end of file diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/bottomsheetdialog/AppInfoBottomSheetFragment.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/bottomsheetdialog/AppInfoBottomSheetFragment.kt index 69f51ad..220aa83 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/bottomsheetdialog/AppInfoBottomSheetFragment.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/bottomsheetdialog/AppInfoBottomSheetFragment.kt @@ -11,6 +11,7 @@ import android.view.ViewGroup import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import androidx.fragment.app.viewModels +import com.github.droidworksstudio.ktx.showLongToast import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.github.droidworksstudio.launcher.R import com.github.droidworksstudio.launcher.data.entities.AppInfo @@ -74,9 +75,12 @@ class AppInfoBottomSheetFragment(private val appInfo: AppInfo) : BottomSheetDial bottomDialogHelper.setupDialogStyle(dialog) binding.run { - bottomSheetFavHidden.text = getString(if (!appInfo.favorite) R.string.bottom_dialog_add_to_home else R.string.bottom_dialog_remove_from_home) - bottomSheetHidden.text = getString(if (!appInfo.hidden) R.string.bottom_dialog_add_to_hidden else R.string.bottom_dialog_remove_to_hidden) - bottomSheetLock.text = getString(if (!appInfo.lock) R.string.bottom_dialog_add_to_lock else R.string.bottom_dialog_remove_to_unlock) + bottomSheetFavHidden.text = + getString(if (!appInfo.favorite) R.string.bottom_dialog_add_to_home else R.string.bottom_dialog_remove_from_home) + bottomSheetHidden.text = + getString(if (!appInfo.hidden) R.string.bottom_dialog_add_to_hidden else R.string.bottom_dialog_remove_to_hidden) + bottomSheetLock.text = + getString(if (!appInfo.lock) R.string.bottom_dialog_add_to_lock else R.string.bottom_dialog_remove_to_unlock) bottomSheetRename.setText(appInfo.appName) bottomSheetOrder.text = appInfo.appOrder.toString() } @@ -121,7 +125,12 @@ class AppInfoBottomSheetFragment(private val appInfo: AppInfo) : BottomSheetDial override fun afterTextChanged(s: Editable?) { if (s.isNullOrEmpty()) { - binding.bottomSheetRename.setHintTextColor(ContextCompat.getColor(requireContext(), R.color.white)) + binding.bottomSheetRename.setHintTextColor( + ContextCompat.getColor( + requireContext(), + R.color.white + ) + ) binding.bottomSheetRename.hint = appName appInfo.appName = appName ?: "" } else { @@ -149,8 +158,7 @@ class AppInfoBottomSheetFragment(private val appInfo: AppInfo) : BottomSheetDial binding.bottomSheetLock.setOnClickListener { if (appInfo.lock) { fingerHelper.startFingerprintAuth(appInfo, this) - } - else { + } else { appInfo.lock = true viewModel.updateAppLock(appInfo, appInfo.lock) dismiss() @@ -175,14 +183,19 @@ class AppInfoBottomSheetFragment(private val appInfo: AppInfo) : BottomSheetDial viewModel.updateAppLock(appInfo, appInfo.lock) dismiss() - appHelper.showToast(requireContext(), getString(R.string.authentication_succeeded)) + requireContext().showLongToast(getString(R.string.authentication_succeeded)) } override fun onAuthenticationFailed() { - appHelper.showToast(requireContext(), getString(R.string.authentication_failed)) + requireContext().showLongToast(getString(R.string.authentication_failed)) } override fun onAuthenticationError(errorCode: Int, errorMessage: CharSequence?) { - appHelper.showToast(requireContext(), getString(R.string.authentication_error)) + requireContext().showLongToast( + getString(R.string.authentication_error).format( + errorMessage, + errorCode + ) + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/bottomsheetdialog/ColorBottomSheetDialogFragment.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/bottomsheetdialog/ColorBottomSheetDialogFragment.kt index a425076..8882a5c 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/bottomsheetdialog/ColorBottomSheetDialogFragment.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/bottomsheetdialog/ColorBottomSheetDialogFragment.kt @@ -8,9 +8,9 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView -import android.widget.Toast import androidx.annotation.RequiresApi import androidx.fragment.app.viewModels +import com.github.droidworksstudio.ktx.showLongToast import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.github.droidworksstudio.launcher.databinding.BottomsheetdialogColorSettingsBinding import com.github.droidworksstudio.launcher.helper.BottomDialogHelper @@ -52,7 +52,7 @@ class ColorBottomSheetDialogFragment : BottomSheetDialogFragment() { observeClickListener() } - private fun initView(){ + private fun initView() { bottomDialogHelper.setupDialogStyle(dialog) binding.selectDateTextColor.apply { @@ -82,7 +82,7 @@ class ColorBottomSheetDialogFragment : BottomSheetDialogFragment() { } - private fun observeClickListener(){ + private fun observeClickListener() { binding.bottomColorDateView.setOnClickListener { showColorPickerDialog( binding.selectDateTextColor, @@ -165,7 +165,7 @@ class ColorBottomSheetDialogFragment : BottomSheetDialogFragment() { } } }) { - Toast.makeText(context, "onCancel", Toast.LENGTH_SHORT).show() + context?.showLongToast("onCancel") } } diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawFragment.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawFragment.kt index 2a7aee2..1ad340b 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawFragment.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawFragment.kt @@ -7,7 +7,6 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Toast import androidx.appcompat.widget.SearchView import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -15,17 +14,18 @@ import androidx.lifecycle.coroutineScope import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.StaggeredGridLayoutManager +import com.github.droidworksstudio.ktx.hideKeyboard +import com.github.droidworksstudio.ktx.openSearch +import com.github.droidworksstudio.ktx.showKeyboard +import com.github.droidworksstudio.ktx.showLongToast import com.github.droidworksstudio.launcher.R import com.github.droidworksstudio.launcher.data.entities.AppInfo import com.github.droidworksstudio.launcher.databinding.FragmentDrawBinding import com.github.droidworksstudio.launcher.helper.AppHelper import com.github.droidworksstudio.launcher.helper.FingerprintHelper import com.github.droidworksstudio.launcher.helper.PreferenceHelper -import com.github.droidworksstudio.launcher.helper.hideKeyboard -import com.github.droidworksstudio.launcher.helper.openSearch import com.github.droidworksstudio.launcher.helper.searchCustomSearchEngine import com.github.droidworksstudio.launcher.helper.searchOnPlayStore -import com.github.droidworksstudio.launcher.helper.showKeyboard import com.github.droidworksstudio.launcher.listener.OnItemClickedListener import com.github.droidworksstudio.launcher.listener.OnSwipeTouchListener import com.github.droidworksstudio.launcher.listener.ScrollEventListener @@ -244,19 +244,21 @@ class DrawFragment : Fragment(), } override fun onAuthenticationSucceeded(appInfo: AppInfo) { - Toast.makeText(context, getString(R.string.authentication_succeeded), Toast.LENGTH_SHORT) - .show() + context.showLongToast(getString(R.string.authentication_succeeded)) appHelper.launchApp(context, appInfo) } override fun onAuthenticationFailed() { - Toast.makeText(context, getString(R.string.authentication_failed), Toast.LENGTH_SHORT) - .show() + context.showLongToast(getString(R.string.authentication_failed)) } override fun onAuthenticationError(errorCode: Int, errorMessage: CharSequence?) { - Toast.makeText(context, getString(R.string.authentication_error), Toast.LENGTH_SHORT) - .show() + context.showLongToast( + getString(R.string.authentication_error).format( + errorMessage, + errorCode + ) + ) } } diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/favorite/FavoriteFragment.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/favorite/FavoriteFragment.kt index 793df7f..fbc9094 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/favorite/FavoriteFragment.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/favorite/FavoriteFragment.kt @@ -8,7 +8,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Toast import androidx.annotation.RequiresApi import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -17,6 +16,7 @@ import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.StaggeredGridLayoutManager +import com.github.droidworksstudio.ktx.showLongToast import com.github.droidworksstudio.launcher.R import com.github.droidworksstudio.launcher.data.entities.AppInfo import com.github.droidworksstudio.launcher.databinding.FragmentFavoriteBinding @@ -214,19 +214,21 @@ class FavoriteFragment : Fragment(), } override fun onAuthenticationSucceeded(appInfo: AppInfo) { - Toast.makeText(context, getString(R.string.authentication_succeeded), Toast.LENGTH_SHORT) - .show() + context.showLongToast(getString(R.string.authentication_succeeded)) appHelper.launchApp(context, appInfo) } override fun onAuthenticationFailed() { - Toast.makeText(context, getString(R.string.authentication_failed), Toast.LENGTH_SHORT) - .show() + context.showLongToast(getString(R.string.authentication_failed)) } override fun onAuthenticationError(errorCode: Int, errorMessage: CharSequence?) { - Toast.makeText(context, getString(R.string.authentication_error), Toast.LENGTH_SHORT) - .show() + context.showLongToast( + getString(R.string.authentication_error).format( + errorMessage, + errorCode + ) + ) } override fun onViewMoved(oldPosition: Int, newPosition: Int): Boolean { diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/hidden/HiddenFragment.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/hidden/HiddenFragment.kt index fc300fe..708a371 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/hidden/HiddenFragment.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/hidden/HiddenFragment.kt @@ -7,13 +7,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Toast import androidx.annotation.RequiresApi import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.StaggeredGridLayoutManager +import com.github.droidworksstudio.ktx.showLongToast import com.github.droidworksstudio.launcher.R import com.github.droidworksstudio.launcher.data.entities.AppInfo import com.github.droidworksstudio.launcher.databinding.FragmentHiddenBinding @@ -155,18 +155,20 @@ class HiddenFragment : Fragment(), } override fun onAuthenticationSucceeded(appInfo: AppInfo) { - Toast.makeText(context, getString(R.string.authentication_succeeded), Toast.LENGTH_SHORT) - .show() + context.showLongToast(getString(R.string.authentication_succeeded)) appHelper.launchApp(context, appInfo) } override fun onAuthenticationFailed() { - Toast.makeText(context, getString(R.string.authentication_failed), Toast.LENGTH_SHORT) - .show() + context.showLongToast(getString(R.string.authentication_failed)) } override fun onAuthenticationError(errorCode: Int, errorMessage: CharSequence?) { - Toast.makeText(context, getString(R.string.authentication_error), Toast.LENGTH_SHORT) - .show() + context.showLongToast( + getString(R.string.authentication_error).format( + errorMessage, + errorCode + ) + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/home/HomeFragment.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/home/HomeFragment.kt index f257600..888ccc6 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/home/HomeFragment.kt @@ -19,6 +19,8 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.StaggeredGridLayoutManager +import com.github.droidworksstudio.ktx.hideKeyboard +import com.github.droidworksstudio.ktx.showLongToast import com.github.droidworksstudio.launcher.R import com.github.droidworksstudio.launcher.accessibility.ActionService import com.github.droidworksstudio.launcher.data.entities.AppInfo @@ -26,7 +28,6 @@ import com.github.droidworksstudio.launcher.databinding.FragmentHomeBinding import com.github.droidworksstudio.launcher.helper.AppHelper import com.github.droidworksstudio.launcher.helper.FingerprintHelper import com.github.droidworksstudio.launcher.helper.PreferenceHelper -import com.github.droidworksstudio.launcher.helper.hideKeyboard import com.github.droidworksstudio.launcher.listener.OnItemClickedListener import com.github.droidworksstudio.launcher.listener.OnSwipeTouchListener import com.github.droidworksstudio.launcher.listener.ScrollEventListener @@ -295,13 +296,11 @@ class HomeFragment : Fragment(), errString: CharSequence ) { when (errorCode) { - BiometricPrompt.ERROR_USER_CANCELED -> appHelper.showToast( - requireContext(), + BiometricPrompt.ERROR_USER_CANCELED -> requireContext().showLongToast( getString(R.string.authentication_cancel) ) - else -> appHelper.showToast( - requireContext(), + else -> requireContext().showLongToast( getString(R.string.authentication_error).format( errString, errorCode @@ -315,10 +314,7 @@ class HomeFragment : Fragment(), } override fun onAuthenticationFailed() { - appHelper.showToast( - requireContext(), - getString(R.string.authentication_failed) - ) + requireContext().showLongToast(getString(R.string.authentication_failed)) } }) @@ -364,23 +360,22 @@ class HomeFragment : Fragment(), override fun onAuthenticationSucceeded(appInfo: AppInfo) { appHelper.launchApp(context, appInfo) - appHelper.showToast(context, getString(R.string.authentication_succeeded)) + requireContext().showLongToast(getString(R.string.authentication_succeeded)) } override fun onAuthenticationFailed() { - appHelper.showToast(context, getString(R.string.authentication_failed)) + requireContext().showLongToast(getString(R.string.authentication_failed)) } override fun onAuthenticationError(errorCode: Int, errorMessage: CharSequence?) { when (errorCode) { - BiometricPrompt.ERROR_USER_CANCELED -> appHelper.showToast( - requireContext(), - getString(R.string.authentication_cancel) - ) + BiometricPrompt.ERROR_USER_CANCELED -> requireContext().showLongToast(getString(R.string.authentication_cancel)) - else -> appHelper.showToast( - requireContext(), - getString(R.string.authentication_error).format(errorMessage, errorCode) + else -> requireContext().showLongToast( + getString(R.string.authentication_error).format( + errorMessage, + errorCode + ) ) } } diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/settings/SettingsFragment.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/settings/SettingsFragment.kt index 5f14b64..59d4424 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/settings/SettingsFragment.kt @@ -7,16 +7,16 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.NavController import androidx.navigation.fragment.findNavController +import com.github.droidworksstudio.ktx.restartApp +import com.github.droidworksstudio.ktx.showLongToast import com.github.droidworksstudio.launcher.R import com.github.droidworksstudio.launcher.databinding.FragmentSettingsBinding import com.github.droidworksstudio.launcher.helper.AppHelper import com.github.droidworksstudio.launcher.helper.PreferenceHelper -import com.github.droidworksstudio.launcher.helper.restartApp import com.github.droidworksstudio.launcher.listener.OnSwipeTouchListener import com.github.droidworksstudio.launcher.listener.ScrollEventListener import com.github.droidworksstudio.launcher.ui.bottomsheetdialog.AlignmentBottomSheetDialogFragment @@ -154,20 +154,12 @@ class SettingsFragment : Fragment(), binding.backupView.setOnClickListener { appHelper.backupSharedPreferences(requireContext()) - Toast.makeText( - requireContext(), - getString(R.string.settings_reload_app_backup), - Toast.LENGTH_SHORT - ).show() + context.showLongToast(getString(R.string.settings_reload_app_backup)) } binding.restoreView.setOnClickListener { appHelper.restoreSharedPreferences(requireContext()) - Toast.makeText( - requireContext(), - getString(R.string.settings_reload_app_restore), - Toast.LENGTH_SHORT - ).show() + context.showLongToast(getString(R.string.settings_reload_app_restore)) restartApp() } }