mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
build: reorganize and reconfigure modules to resolve dependency resolution errors
Gradle, for some reason, does not the path of modules to identify them uniquely. Instead, the 'build id' is used to do so, which consists of the maven group and artifact ID. So, even if two modules are located in separate directories, but have the name group and artifact ID, including both of them in other modules may result in dependency resolution errors.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,21 @@
|
||||
# Flashbar
|
||||
|
||||
Based on [Flashbar](https://github.com/aritraroy/Flashbar).
|
||||
|
||||
## License
|
||||
|
||||
```
|
||||
Copyright 2016 aritraroy
|
||||
|
||||
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.
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import com.itsaky.androidide.build.config.BuildConfig
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
}
|
||||
|
||||
groupConfig {
|
||||
groupIdSuffix.set("utils")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "${BuildConfig.packageName}.flashbar"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.common.utilcode)
|
||||
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.annotation)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.google.material)
|
||||
|
||||
implementation(projects.core.resources)
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ This file is part of AndroidIDE.
|
||||
~
|
||||
~ AndroidIDE is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ AndroidIDE is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<manifest />
|
||||
@@ -0,0 +1,666 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.flashbar
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.Spanned
|
||||
import android.widget.ImageView.ScaleType
|
||||
import android.widget.ImageView.ScaleType.CENTER_CROP
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.flashbar.Flashbar.Gravity.BOTTOM
|
||||
import com.itsaky.androidide.flashbar.Flashbar.Gravity.TOP
|
||||
import com.itsaky.androidide.flashbar.Flashbar.ProgressPosition.LEFT
|
||||
import com.itsaky.androidide.flashbar.Flashbar.ProgressPosition.RIGHT
|
||||
import com.itsaky.androidide.flashbar.anim.FlashAnim
|
||||
import com.itsaky.androidide.flashbar.anim.FlashAnimBarBuilder
|
||||
import com.itsaky.androidide.flashbar.anim.FlashAnimIconBuilder
|
||||
|
||||
private const val DEFAULT_SHADOW_STRENGTH = 4
|
||||
private const val DEFAUT_ICON_SCALE = 1.0f
|
||||
|
||||
@Suppress("UNUSED", "MemberVisibilityCanBePrivate")
|
||||
class Flashbar private constructor(private var builder: Builder) {
|
||||
|
||||
private lateinit var flashbarContainerView: FlashbarContainerView
|
||||
|
||||
lateinit var flashbarView: FlashbarView
|
||||
|
||||
/** Shows a flashbar */
|
||||
fun show() {
|
||||
flashbarContainerView.show(builder.activity)
|
||||
}
|
||||
|
||||
/** Dismisses a flashbar */
|
||||
fun dismiss() {
|
||||
flashbarContainerView.dismiss()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true/false depending on whether the flashbar is showing or not This represents the
|
||||
* partial appearance of the flashbar
|
||||
*/
|
||||
fun isShowing() = flashbarContainerView.isBarShowing()
|
||||
|
||||
/**
|
||||
* Returns true/false depending on whether the flashbar has been completely shown or not This
|
||||
* represents the complete appearance of the flashbar
|
||||
*/
|
||||
fun isShown() = flashbarContainerView.isBarShown()
|
||||
|
||||
private fun construct() {
|
||||
flashbarContainerView = FlashbarContainerView(builder.activity)
|
||||
flashbarContainerView.adjustOrientation(builder.activity)
|
||||
flashbarContainerView.addParent(this)
|
||||
flashbarContainerView.fitsSystemWindows = true
|
||||
|
||||
flashbarView = FlashbarView(builder.activity)
|
||||
flashbarView.init(builder.gravity)
|
||||
flashbarView.adjustWitPositionAndOrientation(builder.activity, builder.gravity)
|
||||
flashbarView.addParent(flashbarContainerView)
|
||||
|
||||
flashbarContainerView.attach(flashbarView)
|
||||
|
||||
initializeContainerDecor()
|
||||
initializeBarDecor()
|
||||
|
||||
flashbarContainerView.construct()
|
||||
}
|
||||
|
||||
private fun initializeContainerDecor() {
|
||||
with(flashbarContainerView) {
|
||||
setDuration(builder.duration)
|
||||
setBarShowListener(builder.onBarShowListener)
|
||||
setBarDismissListener(builder.onBarDismissListener)
|
||||
setBarDismissOnTapOutside(builder.barDismissOnTapOutside)
|
||||
setOnTapOutsideListener(builder.onTapOutsideListener)
|
||||
setOverlay(builder.overlay)
|
||||
setOverlayColor(builder.overlayColor)
|
||||
setOverlayBlockable(builder.overlayBlockable)
|
||||
setVibrationTargets(builder.vibrationTargets)
|
||||
setIconAnim(builder.iconAnimBuilder)
|
||||
|
||||
setEnterAnim(builder.enterAnimBuilder!!)
|
||||
setExitAnim(builder.exitAnimBuilder!!)
|
||||
enableSwipeToDismiss(builder.enableSwipeToDismiss)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeBarDecor() {
|
||||
with(flashbarView) {
|
||||
setBarBackgroundColor(builder.backgroundColor)
|
||||
setBarBackgroundDrawable(builder.backgroundDrawable)
|
||||
setBarTapListener(builder.onBarTapListener)
|
||||
|
||||
setTitle(builder.title)
|
||||
setTitleSpanned(builder.titleSpanned)
|
||||
setTitleTypeface(builder.titleTypeface)
|
||||
setTitleSizeInPx(builder.titleSizeInPx)
|
||||
setTitleSizeInSp(builder.titleSizeInSp)
|
||||
setTitleColor(builder.titleColor)
|
||||
setTitleAppearance(builder.titleAppearance)
|
||||
|
||||
setMessage(builder.message)
|
||||
setMessageSpanned(builder.messageSpanned)
|
||||
setMessageTypeface(builder.messageTypeface)
|
||||
setMessageSizeInPx(builder.messageSizeInPx)
|
||||
setMessageSizeInSp(builder.messageSizeInSp)
|
||||
setMessageColor(builder.messageColor)
|
||||
setMessageAppearance(builder.messageAppearance)
|
||||
|
||||
setPrimaryActionText(builder.primaryActionText)
|
||||
setPrimaryActionTextSpanned(builder.primaryActionTextSpanned)
|
||||
setPrimaryActionTextTypeface(builder.primaryActionTextTypeface)
|
||||
setPrimaryActionTextSizeInPx(builder.primaryActionTextSizeInPx)
|
||||
setPrimaryActionTextSizeInSp(builder.primaryActionTextSizeInSp)
|
||||
setPrimaryActionTextColor(builder.primaryActionTextColor)
|
||||
setPrimaryActionTextAppearance(builder.primaryActionTextAppearance)
|
||||
setPrimaryActionTapListener(builder.onPrimaryActionTapListener)
|
||||
|
||||
setPositiveActionText(builder.positiveActionText)
|
||||
setPositiveActionTextSpanned(builder.positiveActionTextSpanned)
|
||||
setPositiveActionTextTypeface(builder.positiveActionTextTypeface)
|
||||
setPositiveActionTextSizeInPx(builder.positiveActionTextSizeInPx)
|
||||
setPositiveActionTextSizeInSp(builder.positiveActionTextSizeInSp)
|
||||
setPositiveActionTextColor(builder.positiveActionTextColor)
|
||||
setPositiveActionTextAppearance(builder.positiveActionTextAppearance)
|
||||
setPositiveActionTapListener(builder.onPositiveActionTapListener)
|
||||
|
||||
setNegativeActionText(builder.negativeActionText)
|
||||
setNegativeActionTextSpanned(builder.negativeActionTextSpanned)
|
||||
setNegativeActionTextTypeface(builder.negativeActionTextTypeface)
|
||||
setNegativeActionTextSizeInPx(builder.negativeActionTextSizeInPx)
|
||||
setNegativeActionTextSizeInSp(builder.negativeActionTextSizeInSp)
|
||||
setNegativeActionTextColor(builder.negativeActionTextColor)
|
||||
setNegativeActionTextAppearance(builder.negativeActionTextAppearance)
|
||||
setNegativeActionTapListener(builder.onNegativeActionTapListener)
|
||||
|
||||
showIcon(builder.showIcon)
|
||||
showIconScale(builder.iconScale, builder.iconScaleType)
|
||||
setIconDrawable(builder.iconDrawable)
|
||||
setIconBitmap(builder.iconBitmap)
|
||||
setIconColorFilter(builder.iconColorFilter, builder.iconColorFilterMode)
|
||||
|
||||
setProgressPosition(builder.progressPosition)
|
||||
setProgressTint(builder.progressTint, builder.progressPosition)
|
||||
}
|
||||
}
|
||||
|
||||
class Builder(internal var activity: Activity) {
|
||||
internal var gravity: Gravity = BOTTOM
|
||||
internal var backgroundColor: Int? = null
|
||||
internal var backgroundDrawable: Drawable? = null
|
||||
internal var duration: Long = DURATION_INDEFINITE
|
||||
internal var onBarTapListener: OnTapListener? = null
|
||||
internal var onBarShowListener: OnBarShowListener? = null
|
||||
internal var onBarDismissListener: OnBarDismissListener? = null
|
||||
internal var barDismissOnTapOutside: Boolean = false
|
||||
internal var onTapOutsideListener: OnTapListener? = null
|
||||
internal var overlay: Boolean = false
|
||||
internal var overlayColor: Int = ContextCompat.getColor(activity, R.color.modal)
|
||||
internal var overlayBlockable: Boolean = false
|
||||
internal var shadowStrength: Int = DEFAULT_SHADOW_STRENGTH
|
||||
internal var enableSwipeToDismiss: Boolean = false
|
||||
internal var vibrationTargets: List<Vibration> = emptyList()
|
||||
|
||||
internal var title: String? = null
|
||||
internal var titleSpanned: Spanned? = null
|
||||
internal var titleTypeface: Typeface? = null
|
||||
internal var titleSizeInPx: Float? = null
|
||||
internal var titleSizeInSp: Float? = null
|
||||
internal var titleColor: Int? = null
|
||||
internal var titleAppearance: Int? = null
|
||||
|
||||
internal var message: String? = null
|
||||
internal var messageSpanned: Spanned? = null
|
||||
internal var messageTypeface: Typeface? = null
|
||||
internal var messageSizeInPx: Float? = null
|
||||
internal var messageSizeInSp: Float? = null
|
||||
internal var messageColor: Int? = null
|
||||
internal var messageAppearance: Int? = null
|
||||
|
||||
internal var primaryActionText: String? = null
|
||||
internal var primaryActionTextSpanned: Spanned? = null
|
||||
internal var primaryActionTextTypeface: Typeface? = null
|
||||
internal var primaryActionTextSizeInPx: Float? = null
|
||||
internal var primaryActionTextSizeInSp: Float? = null
|
||||
internal var primaryActionTextColor: Int? = null
|
||||
internal var primaryActionTextAppearance: Int? = null
|
||||
internal var onPrimaryActionTapListener: OnActionTapListener? = null
|
||||
|
||||
internal var positiveActionText: String? = null
|
||||
internal var positiveActionTextSpanned: Spanned? = null
|
||||
internal var positiveActionTextTypeface: Typeface? = null
|
||||
internal var positiveActionTextSizeInPx: Float? = null
|
||||
internal var positiveActionTextSizeInSp: Float? = null
|
||||
internal var positiveActionTextColor: Int? = null
|
||||
internal var positiveActionTextAppearance: Int? = null
|
||||
internal var onPositiveActionTapListener: OnActionTapListener? = null
|
||||
|
||||
internal var negativeActionText: String? = null
|
||||
internal var negativeActionTextSpanned: Spanned? = null
|
||||
internal var negativeActionTextTypeface: Typeface? = null
|
||||
internal var negativeActionTextSizeInPx: Float? = null
|
||||
internal var negativeActionTextSizeInSp: Float? = null
|
||||
internal var negativeActionTextColor: Int? = null
|
||||
internal var negativeActionTextAppearance: Int? = null
|
||||
internal var onNegativeActionTapListener: OnActionTapListener? = null
|
||||
|
||||
internal var showIcon: Boolean = false
|
||||
internal var iconScale: Float = DEFAUT_ICON_SCALE
|
||||
internal var iconScaleType: ScaleType = CENTER_CROP
|
||||
internal var iconDrawable: Drawable? = null
|
||||
internal var iconBitmap: Bitmap? = null
|
||||
internal var iconColorFilter: Int? = null
|
||||
internal var iconColorFilterMode: PorterDuff.Mode? = null
|
||||
internal var iconAnimBuilder: FlashAnimIconBuilder? = null
|
||||
|
||||
internal var progressPosition: ProgressPosition? = null
|
||||
internal var progressTint: Int? = null
|
||||
|
||||
internal var enterAnimBuilder: FlashAnimBarBuilder? = null
|
||||
internal var exitAnimBuilder: FlashAnimBarBuilder? = null
|
||||
|
||||
/**
|
||||
* Specifies the gravity from where the flashbar will be shown (top/bottom) Default gravity is
|
||||
* TOP
|
||||
*/
|
||||
fun gravity(gravity: Gravity) = apply { this.gravity = gravity }
|
||||
|
||||
/** Specifies the background drawable of the flashbar */
|
||||
fun backgroundDrawable(drawable: Drawable) = apply { this.backgroundDrawable = drawable }
|
||||
|
||||
/** Specifies the background drawable resource of the flashbar */
|
||||
fun backgroundDrawable(@DrawableRes drawableId: Int) = apply {
|
||||
this.backgroundDrawable = ContextCompat.getDrawable(activity, drawableId)
|
||||
}
|
||||
|
||||
/** Specifies the background color of the flashbar */
|
||||
fun backgroundColor(@ColorInt color: Int) = apply { this.backgroundColor = color }
|
||||
|
||||
/** Specifies the background color resource of the flashbar */
|
||||
fun backgroundColorRes(@ColorRes colorId: Int) = apply {
|
||||
this.backgroundColor = ContextCompat.getColor(activity, colorId)
|
||||
}
|
||||
|
||||
/** Sets listener to receive tap events on the surface of the bar */
|
||||
fun listenBarTaps(listener: OnTapListener) = apply { this.onBarTapListener = listener }
|
||||
|
||||
/**
|
||||
* Specifies the duration for which the flashbar will be visible By default, the duration is
|
||||
* infinite
|
||||
*/
|
||||
fun duration(milliseconds: Long) = apply {
|
||||
require(milliseconds == DURATION_INDEFINITE || milliseconds > 0) { "Duration can only be $DURATION_INDEFINITE or > 0" }
|
||||
this.duration = milliseconds
|
||||
}
|
||||
|
||||
/** Sets listener to receive bar showing/shown events */
|
||||
fun barShowListener(listener: OnBarShowListener) = apply { this.onBarShowListener = listener }
|
||||
|
||||
/** Sets listener to receive bar dismissing/dismissed events */
|
||||
fun barDismissListener(listener: OnBarDismissListener) = apply {
|
||||
this.onBarDismissListener = listener
|
||||
}
|
||||
|
||||
/** Sets listener to receive tap events outside flashbar surface */
|
||||
fun listenOutsideTaps(listener: OnTapListener) = apply { this.onTapOutsideListener = listener }
|
||||
|
||||
/** Dismisses the bar on being tapped outside */
|
||||
fun dismissOnTapOutside() = apply { this.barDismissOnTapOutside = true }
|
||||
|
||||
/** Shows the modal overlay */
|
||||
fun showOverlay() = apply { this.overlay = true }
|
||||
|
||||
/** Specifies modal overlay color */
|
||||
fun overlayColor(@ColorInt color: Int) = apply { this.overlayColor = color }
|
||||
|
||||
/**
|
||||
* Specifies modal overlay color resource Modal overlay is automatically shown if color is set
|
||||
*/
|
||||
fun overlayColorRes(@ColorRes colorId: Int) = apply {
|
||||
this.overlayColor = ContextCompat.getColor(activity, colorId)
|
||||
}
|
||||
|
||||
/** Specifies if modal overlay is blockable and should comsume touch events */
|
||||
fun overlayBlockable() = apply { this.overlayBlockable = true }
|
||||
|
||||
/** Specifies the enter animation of the flashbar */
|
||||
fun enterAnimation(builder: FlashAnimBarBuilder) = apply { this.enterAnimBuilder = builder }
|
||||
|
||||
/** Specifies the exit animation of the flashbar */
|
||||
fun exitAnimation(builder: FlashAnimBarBuilder) = apply { this.exitAnimBuilder = builder }
|
||||
|
||||
/** Enables swipe-to-dismiss for the flashbar */
|
||||
fun enableSwipeToDismiss() = apply { this.enableSwipeToDismiss = true }
|
||||
|
||||
/** Specifies whether the device should vibrate during flashbar enter/exit/both */
|
||||
fun vibrateOn(vararg vibrate: Vibration) = apply {
|
||||
require(vibrate.isNotEmpty()) { "Vibration targets can not be empty" }
|
||||
this.vibrationTargets = vibrate.toList()
|
||||
}
|
||||
|
||||
/** Specifies the title string */
|
||||
fun title(title: String) = apply { this.title = title }
|
||||
|
||||
/** Specifies the title string res */
|
||||
fun title(@StringRes titleId: Int) = apply { this.title = activity.getString(titleId) }
|
||||
|
||||
/** Specifies the title span */
|
||||
fun title(title: Spanned) = apply { this.titleSpanned = title }
|
||||
|
||||
/** Specifies the title typeface */
|
||||
fun titleTypeface(typeface: Typeface) = apply { this.titleTypeface = typeface }
|
||||
|
||||
/** Specifies the title size (in pixels) */
|
||||
fun titleSizeInPx(size: Float) = apply { this.titleSizeInPx = size }
|
||||
|
||||
/** Specifies the title size (in sp) */
|
||||
fun titleSizeInSp(size: Float) = apply { this.titleSizeInSp = size }
|
||||
|
||||
/** Specifies the title color */
|
||||
fun titleColor(@ColorInt color: Int) = apply { this.titleColor = color }
|
||||
|
||||
/** Specifies the title color resource */
|
||||
fun titleColorRes(@ColorRes colorId: Int) = apply {
|
||||
this.titleColor = ContextCompat.getColor(activity, colorId)
|
||||
}
|
||||
|
||||
/** Specifies the title appearance */
|
||||
fun titleAppearance(@StyleRes appearance: Int) = apply { this.titleAppearance = appearance }
|
||||
|
||||
/** Specifies the message string */
|
||||
fun message(message: String) = apply { this.message = message }
|
||||
|
||||
/** Specifies the message string resource */
|
||||
fun message(@StringRes messageId: Int) = apply { this.message = activity.getString(messageId) }
|
||||
|
||||
/** Specifies the message string span */
|
||||
fun message(message: Spanned) = apply { this.messageSpanned = message }
|
||||
|
||||
/** Specifies the message typeface */
|
||||
fun messageTypeface(typeface: Typeface) = apply { this.messageTypeface = typeface }
|
||||
|
||||
/** Specifies the message size (in pixels) */
|
||||
fun messageSizeInPx(size: Float) = apply { this.messageSizeInPx = size }
|
||||
|
||||
/** Specifies the message size (in sp) */
|
||||
fun messageSizeInSp(size: Float) = apply { this.messageSizeInSp = size }
|
||||
|
||||
/** Specifies the message color */
|
||||
fun messageColor(@ColorInt color: Int) = apply { this.messageColor = color }
|
||||
|
||||
/** Specifies the message color resource */
|
||||
fun messageColorRes(@ColorRes colorId: Int) = apply {
|
||||
this.messageColor = ContextCompat.getColor(activity, colorId)
|
||||
}
|
||||
|
||||
/** Specifies the message appearance */
|
||||
fun messageAppearance(@StyleRes appearance: Int) = apply { this.messageAppearance = appearance }
|
||||
|
||||
/** Specifies the primary action text string */
|
||||
fun primaryActionText(text: String) = apply {
|
||||
require(progressPosition != RIGHT) { "Cannot show action button if right progress is set" }
|
||||
this.primaryActionText = text
|
||||
}
|
||||
|
||||
/** Specifies the primary action text string resource */
|
||||
fun primaryActionText(@StringRes actionTextId: Int) = apply {
|
||||
primaryActionText(activity.getString(actionTextId))
|
||||
}
|
||||
|
||||
/** Specifies the primary action text string span */
|
||||
fun primaryActionText(actionText: Spanned) = apply {
|
||||
this.primaryActionTextSpanned = actionText
|
||||
}
|
||||
|
||||
/** Specifies the primary action text typeface */
|
||||
fun primaryActionTextTypeface(typeface: Typeface) = apply {
|
||||
this.primaryActionTextTypeface = typeface
|
||||
}
|
||||
|
||||
/** Specifies the primary action text size (in pixels) */
|
||||
fun primaryActionTextSizeInPx(size: Float) = apply { this.primaryActionTextSizeInPx = size }
|
||||
|
||||
/** Specifies the primary action text size (in sp) */
|
||||
fun primaryActionTextSizeInSp(size: Float) = apply { this.primaryActionTextSizeInSp = size }
|
||||
|
||||
/** Specifies the primary action text color */
|
||||
fun primaryActionTextColor(@ColorInt color: Int) = apply { this.primaryActionTextColor = color }
|
||||
|
||||
/** Specifies the primary action text color resource */
|
||||
fun primaryActionTextColorRes(@ColorRes colorId: Int) = apply {
|
||||
this.primaryActionTextColor = ContextCompat.getColor(activity, colorId)
|
||||
}
|
||||
|
||||
/** Specifies the primary action text appearance */
|
||||
fun primaryActionTextAppearance(@StyleRes appearance: Int) = apply {
|
||||
this.primaryActionTextAppearance = appearance
|
||||
}
|
||||
|
||||
/** Sets listener to receive tap events on the primary action */
|
||||
fun primaryActionTapListener(onActionTapListener: OnActionTapListener) = apply {
|
||||
this.onPrimaryActionTapListener = onActionTapListener
|
||||
}
|
||||
|
||||
/** Specifies the positive action text string */
|
||||
fun positiveActionText(text: String) = apply { this.positiveActionText = text }
|
||||
|
||||
/** Specifies the positive action text string resource */
|
||||
fun positiveActionText(@StringRes actionTextId: Int) = apply {
|
||||
positiveActionText(activity.getString(actionTextId))
|
||||
}
|
||||
|
||||
/** Specifies the positive action text string span */
|
||||
fun positiveActionText(actionText: Spanned) = apply {
|
||||
this.positiveActionTextSpanned = actionText
|
||||
}
|
||||
|
||||
/** Specifies the positive action text typeface */
|
||||
fun positiveActionTextTypeface(typeface: Typeface) = apply {
|
||||
this.positiveActionTextTypeface = typeface
|
||||
}
|
||||
|
||||
/** Specifies the positive action text size (in pixels) */
|
||||
fun positiveActionTextSizeInPx(size: Float) = apply { this.positiveActionTextSizeInPx = size }
|
||||
|
||||
/** Specifies the positive action text size (in sp) */
|
||||
fun positiveActionTextSizeInSp(size: Float) = apply { this.positiveActionTextSizeInSp = size }
|
||||
|
||||
/** Specifies the positive action text color */
|
||||
fun positiveActionTextColor(@ColorInt color: Int) = apply {
|
||||
this.positiveActionTextColor = color
|
||||
}
|
||||
|
||||
/** Specifies the positive action text color resource */
|
||||
fun positiveActionTextColorRes(@ColorRes colorId: Int) = apply {
|
||||
this.positiveActionTextColor = ContextCompat.getColor(activity, colorId)
|
||||
}
|
||||
|
||||
/** Specifies the positive action text appearance */
|
||||
fun positiveActionTextAppearance(@StyleRes appearance: Int) = apply {
|
||||
this.positiveActionTextAppearance = appearance
|
||||
}
|
||||
|
||||
/** Sets listener to receive tap events on the positive action */
|
||||
fun positiveActionTapListener(onActionTapListener: OnActionTapListener) = apply {
|
||||
this.onPositiveActionTapListener = onActionTapListener
|
||||
}
|
||||
|
||||
/** Specifies the negative action text string */
|
||||
fun negativeActionText(text: String) = apply { this.negativeActionText = text }
|
||||
|
||||
/** Specifies the negative action text string resource */
|
||||
fun negativeActionText(@StringRes actionTextId: Int) = apply {
|
||||
negativeActionText(activity.getString(actionTextId))
|
||||
}
|
||||
|
||||
/** Specifies the negative action text string span */
|
||||
fun negativeActionText(actionText: Spanned) = apply {
|
||||
this.negativeActionTextSpanned = actionText
|
||||
}
|
||||
|
||||
/** Specifies the negative action text typeface */
|
||||
fun negativeActionTextTypeface(typeface: Typeface) = apply {
|
||||
this.negativeActionTextTypeface = typeface
|
||||
}
|
||||
|
||||
/** Specifies the negative action text size (in pixels) */
|
||||
fun negativeActionTextSizeInPx(size: Float) = apply { this.negativeActionTextSizeInPx = size }
|
||||
|
||||
/** Specifies the negative action text size (in sp) */
|
||||
fun negativeActionTextSizeInSp(size: Float) = apply { this.negativeActionTextSizeInSp = size }
|
||||
|
||||
/** Specifies the negative action text color */
|
||||
fun negativeActionTextColor(@ColorInt color: Int) = apply {
|
||||
this.negativeActionTextColor = color
|
||||
}
|
||||
|
||||
/** Specifies the negative action text color resource */
|
||||
fun negativeActionTextColorRes(@ColorRes colorId: Int) = apply {
|
||||
this.negativeActionTextColor = ContextCompat.getColor(activity, colorId)
|
||||
}
|
||||
|
||||
/** Specifies the negative action text appearance */
|
||||
fun negativeActionTextAppearance(@StyleRes appearance: Int) = apply {
|
||||
this.negativeActionTextAppearance = appearance
|
||||
}
|
||||
|
||||
/** Sets listener to receive tap events on the negative action */
|
||||
fun negativeActionTapListener(onActionTapListener: OnActionTapListener) = apply {
|
||||
this.onNegativeActionTapListener = onActionTapListener
|
||||
}
|
||||
|
||||
/** Specifies if the icon should be shown. Also configures its scale factor and scale type */
|
||||
@JvmOverloads
|
||||
fun showIcon(scale: Float = DEFAUT_ICON_SCALE, scaleType: ScaleType = CENTER_CROP) = apply {
|
||||
require(progressPosition != LEFT) { "Cannot show icon if left progress is set" }
|
||||
require(scale > 0) { "Icon scale cannot be negative or zero" }
|
||||
|
||||
this.showIcon = true
|
||||
this.iconScale = scale
|
||||
this.iconScaleType = scaleType
|
||||
}
|
||||
|
||||
/** Specifies the icon drawable */
|
||||
fun icon(icon: Drawable) = apply { this.iconDrawable = icon }
|
||||
|
||||
/** Specifies the icon drawable resource */
|
||||
fun icon(@DrawableRes iconId: Int) = apply {
|
||||
this.iconDrawable = ContextCompat.getDrawable(activity, iconId)
|
||||
}
|
||||
|
||||
/** Specifies the icon bitmap */
|
||||
fun icon(bitmap: Bitmap) = apply { this.iconBitmap = bitmap }
|
||||
|
||||
/** Specifies the icon color filter and mode */
|
||||
@JvmOverloads
|
||||
fun iconColorFilter(@ColorInt color: Int, mode: PorterDuff.Mode? = null) = apply {
|
||||
this.iconColorFilter = color
|
||||
this.iconColorFilterMode = mode
|
||||
}
|
||||
|
||||
/** Specifies the icon color filter resource and mode */
|
||||
@JvmOverloads
|
||||
fun iconColorFilterRes(@ColorRes colorId: Int, mode: PorterDuff.Mode? = null) = apply {
|
||||
this.iconColorFilter = ContextCompat.getColor(activity, colorId)
|
||||
this.iconColorFilterMode = mode
|
||||
}
|
||||
|
||||
/** Specifies the icon builder */
|
||||
fun iconAnimation(builder: FlashAnimIconBuilder) = apply { this.iconAnimBuilder = builder }
|
||||
|
||||
/** Specifies the gravity in which the indeterminate progress is shown (left/right) */
|
||||
fun showProgress(position: ProgressPosition) = apply {
|
||||
this.progressPosition = position
|
||||
|
||||
if (progressPosition == LEFT && showIcon) {
|
||||
throw IllegalArgumentException("Cannot show progress at left if icon is already set")
|
||||
}
|
||||
|
||||
if (progressPosition == RIGHT && primaryActionText != null) {
|
||||
throw IllegalArgumentException(
|
||||
"Cannot show progress at right if action button is already set"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Specifies the indeterminate progress tint */
|
||||
fun progressTint(@ColorInt color: Int) = apply { this.progressTint = color }
|
||||
|
||||
/** Specifies the indeterminate progress tint resource */
|
||||
fun progressTintRes(@ColorRes colorId: Int) = apply {
|
||||
this.progressTint = ContextCompat.getColor(activity, colorId)
|
||||
}
|
||||
|
||||
/** Builds a flashbar instance */
|
||||
fun build(): Flashbar {
|
||||
configureAnimation()
|
||||
val flashbar = Flashbar(this)
|
||||
flashbar.construct()
|
||||
return flashbar
|
||||
}
|
||||
|
||||
/** Shows the flashbar */
|
||||
fun show() = build().show()
|
||||
|
||||
private fun configureAnimation() {
|
||||
enterAnimBuilder =
|
||||
if (enterAnimBuilder == null) {
|
||||
when (gravity) {
|
||||
TOP -> FlashAnim.with(activity).animateBar().enter().fromTop()
|
||||
BOTTOM -> FlashAnim.with(activity).animateBar().enter().fromBottom()
|
||||
}
|
||||
} else {
|
||||
when (gravity) {
|
||||
TOP -> enterAnimBuilder!!.enter().fromTop()
|
||||
BOTTOM -> enterAnimBuilder!!.enter().fromBottom()
|
||||
}
|
||||
}
|
||||
|
||||
exitAnimBuilder =
|
||||
if (exitAnimBuilder == null) {
|
||||
when (gravity) {
|
||||
TOP -> FlashAnim.with(activity).animateBar().exit().fromTop()
|
||||
BOTTOM -> FlashAnim.with(activity).animateBar().exit().fromBottom()
|
||||
}
|
||||
} else {
|
||||
when (gravity) {
|
||||
TOP -> exitAnimBuilder!!.exit().fromTop()
|
||||
BOTTOM -> exitAnimBuilder!!.exit().fromBottom()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DURATION_SHORT = 1000L
|
||||
const val DURATION_LONG = 2500L
|
||||
const val DURATION_INDEFINITE = -1L
|
||||
}
|
||||
|
||||
enum class Gravity {
|
||||
TOP,
|
||||
BOTTOM
|
||||
}
|
||||
|
||||
enum class DismissEvent {
|
||||
TIMEOUT,
|
||||
MANUAL,
|
||||
TAP_OUTSIDE,
|
||||
SWIPE
|
||||
}
|
||||
|
||||
enum class Vibration {
|
||||
SHOW,
|
||||
DISMISS
|
||||
}
|
||||
|
||||
enum class ProgressPosition {
|
||||
LEFT,
|
||||
RIGHT
|
||||
}
|
||||
|
||||
fun interface OnActionTapListener {
|
||||
fun onActionTapped(bar: Flashbar)
|
||||
}
|
||||
|
||||
interface OnBarDismissListener {
|
||||
fun onDismissing(bar: Flashbar, isSwiped: Boolean)
|
||||
fun onDismissProgress(bar: Flashbar, progress: Float)
|
||||
fun onDismissed(bar: Flashbar, event: DismissEvent)
|
||||
}
|
||||
|
||||
interface OnTapListener {
|
||||
fun onTap(flashbar: Flashbar)
|
||||
}
|
||||
|
||||
interface OnBarShowListener {
|
||||
fun onShowing(bar: Flashbar)
|
||||
fun onShowProgress(bar: Flashbar, progress: Float)
|
||||
fun onShown(bar: Flashbar)
|
||||
}
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.flashbar
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.view.HapticFeedbackConstants.VIRTUAL_KEY
|
||||
import android.view.MotionEvent
|
||||
import android.view.MotionEvent.ACTION_DOWN
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.widget.RelativeLayout
|
||||
import com.itsaky.androidide.flashbar.Flashbar.Companion.DURATION_INDEFINITE
|
||||
import com.itsaky.androidide.flashbar.Flashbar.DismissEvent
|
||||
import com.itsaky.androidide.flashbar.Flashbar.DismissEvent.*
|
||||
import com.itsaky.androidide.flashbar.Flashbar.Vibration.DISMISS
|
||||
import com.itsaky.androidide.flashbar.SwipeDismissTouchListener.DismissCallbacks
|
||||
import com.itsaky.androidide.flashbar.anim.FlashAnim
|
||||
import com.itsaky.androidide.flashbar.Flashbar.OnBarDismissListener
|
||||
import com.itsaky.androidide.flashbar.Flashbar.OnBarShowListener
|
||||
import com.itsaky.androidide.flashbar.Flashbar.OnTapListener
|
||||
import com.itsaky.androidide.flashbar.Flashbar.Vibration
|
||||
import com.itsaky.androidide.flashbar.Flashbar.Vibration.SHOW
|
||||
import com.itsaky.androidide.flashbar.anim.FlashAnimBarBuilder
|
||||
import com.itsaky.androidide.flashbar.anim.FlashAnimIconBuilder
|
||||
import com.itsaky.androidide.flashbar.util.NavigationBarPosition.*
|
||||
import com.itsaky.androidide.flashbar.util.afterMeasured
|
||||
import com.itsaky.androidide.flashbar.util.getNavigationBarPosition
|
||||
import com.itsaky.androidide.flashbar.util.getNavigationBarSizeInPx
|
||||
import com.itsaky.androidide.flashbar.util.getRootView
|
||||
|
||||
/**
|
||||
* Container withView matching the height and width of the parent to hold a FlashbarView.
|
||||
* It will occupy the entire screens size but will be completely transparent. The
|
||||
* FlashbarView inside is the only visible component in it.
|
||||
*/
|
||||
internal class FlashbarContainerView(context: Context)
|
||||
: RelativeLayout(context), DismissCallbacks {
|
||||
|
||||
private val dismissRunnable = Runnable { dismissInternal(TIMEOUT) }
|
||||
|
||||
internal lateinit var parentFlashbar: Flashbar
|
||||
|
||||
private lateinit var flashbarView: FlashbarView
|
||||
|
||||
private lateinit var enterAnimBuilder: FlashAnimBarBuilder
|
||||
private lateinit var exitAnimBuilder: FlashAnimBarBuilder
|
||||
private lateinit var vibrationTargets: List<Vibration>
|
||||
|
||||
private var onBarShowListener: OnBarShowListener? = null
|
||||
private var onBarDismissListener: OnBarDismissListener? = null
|
||||
private var onTapOutsideListener: OnTapListener? = null
|
||||
private var overlayColor: Int? = null
|
||||
private var iconAnimBuilder: FlashAnimIconBuilder? = null
|
||||
|
||||
private var duration = DURATION_INDEFINITE
|
||||
private var isBarShowing = false
|
||||
private var isBarShown = false
|
||||
private var isBarDismissing = false
|
||||
private var barDismissOnTapOutside: Boolean = false
|
||||
private var earlyDismissalRequested = false
|
||||
private var showOverlay: Boolean = false
|
||||
private var overlayBlockable: Boolean = false
|
||||
|
||||
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
||||
when (event.action) {
|
||||
ACTION_DOWN -> {
|
||||
val rect = Rect()
|
||||
flashbarView.getHitRect(rect)
|
||||
|
||||
// Checks if the tap was outside the bar
|
||||
if (!rect.contains(event.x.toInt(), event.y.toInt())) {
|
||||
onTapOutsideListener?.onTap(parentFlashbar)
|
||||
|
||||
if (barDismissOnTapOutside) {
|
||||
dismissInternal(TAP_OUTSIDE)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.onInterceptTouchEvent(event)
|
||||
}
|
||||
|
||||
override fun onSwipe(isSwiping: Boolean) {
|
||||
isBarDismissing = isSwiping
|
||||
if (isSwiping) {
|
||||
onBarDismissListener?.onDismissing(parentFlashbar, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDismiss(view: View) {
|
||||
removeCallbacks(dismissRunnable)
|
||||
|
||||
(parent as? ViewGroup)?.removeView(this@FlashbarContainerView)
|
||||
isBarShown = false
|
||||
|
||||
flashbarView.stopIconAnimation()
|
||||
|
||||
if (vibrationTargets.contains(DISMISS)) {
|
||||
performHapticFeedback(VIRTUAL_KEY)
|
||||
}
|
||||
|
||||
onBarDismissListener?.onDismissed(parentFlashbar, SWIPE)
|
||||
}
|
||||
|
||||
internal fun attach(flashbarView: FlashbarView) {
|
||||
this.flashbarView = flashbarView
|
||||
}
|
||||
|
||||
internal fun construct() {
|
||||
isHapticFeedbackEnabled = true
|
||||
|
||||
if (showOverlay) {
|
||||
setBackgroundColor(overlayColor!!)
|
||||
|
||||
if (overlayBlockable) {
|
||||
isClickable = true
|
||||
isFocusable = true
|
||||
}
|
||||
}
|
||||
|
||||
addView(flashbarView)
|
||||
}
|
||||
|
||||
internal fun addParent(flashbar: Flashbar) {
|
||||
this.parentFlashbar = flashbar
|
||||
}
|
||||
|
||||
internal fun adjustOrientation(activity: Activity) {
|
||||
val flashbarContainerViewLp = LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||
|
||||
val navigationBarPosition = activity.getNavigationBarPosition()
|
||||
val navigationBarSize = activity.getNavigationBarSizeInPx()
|
||||
|
||||
when (navigationBarPosition) {
|
||||
LEFT -> flashbarContainerViewLp.leftMargin = navigationBarSize
|
||||
TOP -> flashbarContainerViewLp.topMargin = navigationBarSize
|
||||
RIGHT -> flashbarContainerViewLp.rightMargin = navigationBarSize
|
||||
BOTTOM -> flashbarContainerViewLp.bottomMargin = navigationBarSize
|
||||
}
|
||||
|
||||
layoutParams = flashbarContainerViewLp
|
||||
}
|
||||
|
||||
|
||||
internal fun show(activity: Activity) {
|
||||
if (isBarShowing || isBarShown) return
|
||||
|
||||
val activityRootView = activity.getRootView() ?: return
|
||||
|
||||
// Only add the withView to the parent once
|
||||
if (this.parent == null) activityRootView.addView(this)
|
||||
|
||||
activityRootView.afterMeasured {
|
||||
val enterAnim = enterAnimBuilder.withView(flashbarView).build()
|
||||
enterAnim.start(object : FlashAnim.InternalAnimListener {
|
||||
override fun onStart() {
|
||||
isBarShowing = true
|
||||
onBarShowListener?.onShowing(parentFlashbar)
|
||||
}
|
||||
|
||||
override fun onUpdate(progress: Float) {
|
||||
onBarShowListener?.onShowProgress(parentFlashbar, progress)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
isBarShowing = false
|
||||
isBarShown = true
|
||||
|
||||
flashbarView.startIconAnimation(iconAnimBuilder)
|
||||
|
||||
if (vibrationTargets.contains(SHOW)) {
|
||||
performHapticFeedback(VIRTUAL_KEY)
|
||||
}
|
||||
|
||||
onBarShowListener?.onShown(parentFlashbar)
|
||||
|
||||
if (earlyDismissalRequested) {
|
||||
dismiss()
|
||||
earlyDismissalRequested = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
handleDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun dismiss() {
|
||||
dismissInternal(MANUAL)
|
||||
}
|
||||
|
||||
internal fun isBarShowing() = isBarShowing
|
||||
|
||||
internal fun isBarShown() = isBarShown
|
||||
|
||||
internal fun setDuration(duration: Long) {
|
||||
this.duration = duration
|
||||
}
|
||||
|
||||
internal fun setBarShowListener(listener: OnBarShowListener?) {
|
||||
this.onBarShowListener = listener
|
||||
}
|
||||
|
||||
internal fun setBarDismissListener(listener: OnBarDismissListener?) {
|
||||
this.onBarDismissListener = listener
|
||||
}
|
||||
|
||||
internal fun setBarDismissOnTapOutside(dismiss: Boolean) {
|
||||
this.barDismissOnTapOutside = dismiss
|
||||
}
|
||||
|
||||
internal fun setOnTapOutsideListener(listener: OnTapListener?) {
|
||||
this.onTapOutsideListener = listener
|
||||
}
|
||||
|
||||
internal fun setOverlay(overlay: Boolean) {
|
||||
this.showOverlay = overlay
|
||||
}
|
||||
|
||||
internal fun setOverlayColor(color: Int) {
|
||||
this.overlayColor = color
|
||||
}
|
||||
|
||||
internal fun setOverlayBlockable(blockable: Boolean) {
|
||||
this.overlayBlockable = blockable
|
||||
}
|
||||
|
||||
internal fun setEnterAnim(builder: FlashAnimBarBuilder) {
|
||||
this.enterAnimBuilder = builder
|
||||
}
|
||||
|
||||
internal fun setExitAnim(builder: FlashAnimBarBuilder) {
|
||||
this.exitAnimBuilder = builder
|
||||
}
|
||||
|
||||
internal fun enableSwipeToDismiss(enable: Boolean) {
|
||||
this.flashbarView.enableSwipeToDismiss(enable, this)
|
||||
}
|
||||
|
||||
internal fun setVibrationTargets(targets: List<Vibration>) {
|
||||
this.vibrationTargets = targets
|
||||
}
|
||||
|
||||
internal fun setIconAnim(builder: FlashAnimIconBuilder?) {
|
||||
this.iconAnimBuilder = builder
|
||||
}
|
||||
|
||||
private fun handleDismiss() {
|
||||
if (duration != DURATION_INDEFINITE) {
|
||||
postDelayed(dismissRunnable, duration)
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismissInternal(event: DismissEvent) {
|
||||
if (isBarShowing) {
|
||||
// Flashbar is currently being shown
|
||||
// but a dismissal has been requested
|
||||
// take this into account and dismiss flashbar afterwards
|
||||
earlyDismissalRequested = true
|
||||
return
|
||||
}
|
||||
|
||||
if (isBarDismissing || !isBarShown) {
|
||||
return
|
||||
}
|
||||
|
||||
removeCallbacks(dismissRunnable)
|
||||
|
||||
val exitAnim = exitAnimBuilder.withView(flashbarView).build()
|
||||
exitAnim.start(object : FlashAnim.InternalAnimListener {
|
||||
override fun onStart() {
|
||||
isBarDismissing = true
|
||||
onBarDismissListener?.onDismissing(parentFlashbar, false)
|
||||
}
|
||||
|
||||
override fun onUpdate(progress: Float) {
|
||||
onBarDismissListener?.onDismissProgress(parentFlashbar, progress)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
isBarDismissing = false
|
||||
isBarShown = false
|
||||
|
||||
if (vibrationTargets.contains(DISMISS)) {
|
||||
performHapticFeedback(VIRTUAL_KEY)
|
||||
}
|
||||
|
||||
onBarDismissListener?.onDismissed(parentFlashbar, event)
|
||||
|
||||
post { (parent as? ViewGroup)?.removeView(this@FlashbarContainerView) }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.flashbar
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.Spanned
|
||||
import android.text.TextUtils
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM
|
||||
import android.widget.RelativeLayout.ALIGN_PARENT_TOP
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.view.marginBottom
|
||||
import androidx.core.view.marginLeft
|
||||
import androidx.core.view.marginRight
|
||||
import androidx.core.view.marginTop
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
import com.itsaky.androidide.flashbar.Flashbar.Gravity
|
||||
import com.itsaky.androidide.flashbar.Flashbar.Gravity.BOTTOM
|
||||
import com.itsaky.androidide.flashbar.Flashbar.Gravity.TOP
|
||||
import com.itsaky.androidide.flashbar.Flashbar.OnActionTapListener
|
||||
import com.itsaky.androidide.flashbar.Flashbar.OnTapListener
|
||||
import com.itsaky.androidide.flashbar.Flashbar.ProgressPosition
|
||||
import com.itsaky.androidide.flashbar.Flashbar.ProgressPosition.LEFT
|
||||
import com.itsaky.androidide.flashbar.Flashbar.ProgressPosition.RIGHT
|
||||
import com.itsaky.androidide.flashbar.SwipeDismissTouchListener.DismissCallbacks
|
||||
import com.itsaky.androidide.flashbar.anim.FlashAnimIconBuilder
|
||||
import com.itsaky.androidide.flashbar.databinding.FlashBarViewBinding
|
||||
import com.itsaky.androidide.flashbar.util.getStatusBarHeightInPx
|
||||
|
||||
/**
|
||||
* The actual Flashbar withView representation that can consist of the title, message, button, icon,
|
||||
* etc. Its size is adaptive and depends solely on the amount of content present in it. It always
|
||||
* matches the width of the screen.
|
||||
*
|
||||
* It can either be present at the top or at the bottom of the screen. It will always consume touch
|
||||
* events and respond as necessary.
|
||||
*/
|
||||
class FlashbarView(context: Context) : LinearLayout(context) {
|
||||
|
||||
private val compensationMarginTop =
|
||||
resources.getDimension(R.dimen.fb_top_compensation_margin).toInt()
|
||||
private val compensationMarginBottom =
|
||||
resources.getDimension(R.dimen.fb_bottom_compensation_margin).toInt()
|
||||
|
||||
private lateinit var binding: FlashBarViewBinding
|
||||
private lateinit var parentFlashbarContainer: FlashbarContainerView
|
||||
private lateinit var gravity: Gravity
|
||||
|
||||
private var isMarginCompensationApplied: Boolean = false
|
||||
|
||||
private val fbContent: LinearLayout
|
||||
get() = this.binding.fbContent
|
||||
|
||||
private val fbRoot: LinearLayout
|
||||
get() = this.binding.fbRoot
|
||||
|
||||
private val fbIcon: ImageView
|
||||
get() = this.binding.fbIcon
|
||||
|
||||
private val fbLeftProgress: CircularProgressIndicator
|
||||
get() = this.binding.fbLeftProgress
|
||||
|
||||
private val fbRightProgress: CircularProgressIndicator
|
||||
get() = this.binding.fbRightProgress
|
||||
|
||||
private val fbMessage: TextView
|
||||
get() = this.binding.fbMessage
|
||||
|
||||
private val fbPrimaryAction: MaterialButton
|
||||
get() = this.binding.fbPrimaryAction
|
||||
|
||||
private val fbPositiveAction: MaterialButton
|
||||
get() = this.binding.fbPositiveAction
|
||||
|
||||
private val fbNegativeAction: MaterialButton
|
||||
get() = this.binding.fbNegativeAction
|
||||
|
||||
private val fbSecondaryActionContainer: LinearLayout
|
||||
get() = this.binding.fbSecondaryActionContainer
|
||||
|
||||
private val fbTitle: TextView
|
||||
get() = this.binding.fbTitle
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
if (!isMarginCompensationApplied) {
|
||||
isMarginCompensationApplied = true
|
||||
|
||||
val params = layoutParams as MarginLayoutParams
|
||||
when (gravity) {
|
||||
TOP -> params.topMargin = -compensationMarginTop
|
||||
BOTTOM -> params.bottomMargin = -compensationMarginBottom
|
||||
}
|
||||
requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun init(gravity: Gravity) {
|
||||
this.gravity = gravity
|
||||
this.orientation = VERTICAL
|
||||
this.binding = FlashBarViewBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
|
||||
this.binding.root.apply {
|
||||
// add margin to the card view so that the card elevation is visible
|
||||
val dp16 = context.resources.getDimensionPixelSize(R.dimen.fb_card_elevation)
|
||||
val (topIncr, bottomIncr) = when (gravity) {
|
||||
TOP -> 0 to dp16
|
||||
BOTTOM -> dp16 to 0
|
||||
}
|
||||
|
||||
updateLayoutParams<MarginLayoutParams> {
|
||||
setMargins(
|
||||
/* left = */ marginLeft,
|
||||
/* top = */ marginTop + topIncr,
|
||||
/* right = */ marginRight,
|
||||
/* bottom = */ marginBottom + bottomIncr
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun adjustWitPositionAndOrientation(activity: Activity, gravity: Gravity) {
|
||||
val flashbarViewLp = RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
|
||||
val statusBarHeight = activity.getStatusBarHeightInPx()
|
||||
|
||||
val flashbarViewContentLp = fbContent.layoutParams as LayoutParams
|
||||
|
||||
when (gravity) {
|
||||
TOP -> {
|
||||
flashbarViewContentLp.topMargin = statusBarHeight.plus(compensationMarginTop / 2)
|
||||
flashbarViewLp.addRule(ALIGN_PARENT_TOP)
|
||||
}
|
||||
|
||||
BOTTOM -> {
|
||||
flashbarViewContentLp.bottomMargin = compensationMarginBottom
|
||||
flashbarViewLp.addRule(ALIGN_PARENT_BOTTOM)
|
||||
}
|
||||
}
|
||||
fbContent.layoutParams = flashbarViewContentLp
|
||||
layoutParams = flashbarViewLp
|
||||
}
|
||||
|
||||
internal fun addParent(flashbarContainerView: FlashbarContainerView) {
|
||||
this.parentFlashbarContainer = flashbarContainerView
|
||||
}
|
||||
|
||||
fun setBarBackgroundDrawable(drawable: Drawable?) {
|
||||
if (drawable == null) return
|
||||
this.fbRoot.background = drawable
|
||||
}
|
||||
|
||||
fun setBarBackgroundColor(@ColorInt color: Int?) {
|
||||
if (color == null) return
|
||||
this.fbRoot.setBackgroundColor(color)
|
||||
}
|
||||
|
||||
fun setBarTapListener(listener: OnTapListener?) {
|
||||
if (listener == null) return
|
||||
|
||||
this.fbRoot.setOnClickListener { listener.onTap(parentFlashbarContainer.parentFlashbar) }
|
||||
}
|
||||
|
||||
fun setTitle(title: String?) {
|
||||
if (TextUtils.isEmpty(title)) return
|
||||
|
||||
this.fbTitle.text = title
|
||||
this.fbTitle.visibility = VISIBLE
|
||||
}
|
||||
|
||||
fun setTitleSpanned(title: Spanned?) {
|
||||
if (title == null) return
|
||||
|
||||
this.fbTitle.text = title
|
||||
this.fbTitle.visibility = VISIBLE
|
||||
}
|
||||
|
||||
fun setTitleTypeface(typeface: Typeface?) {
|
||||
if (typeface == null) return
|
||||
fbTitle.typeface = typeface
|
||||
}
|
||||
|
||||
fun setTitleSizeInPx(size: Float?) {
|
||||
if (size == null) return
|
||||
fbTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, size)
|
||||
}
|
||||
|
||||
fun setTitleSizeInSp(size: Float?) {
|
||||
if (size == null) return
|
||||
fbTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, size)
|
||||
}
|
||||
|
||||
fun setTitleColor(color: Int?) {
|
||||
if (color == null) return
|
||||
fbTitle.setTextColor(color)
|
||||
}
|
||||
|
||||
fun setTitleAppearance(titleAppearance: Int?) {
|
||||
if (titleAppearance == null) return
|
||||
this.fbTitle.setTextAppearance(titleAppearance)
|
||||
}
|
||||
|
||||
fun setMessage(message: String?) {
|
||||
if (TextUtils.isEmpty(message)) return
|
||||
|
||||
this.fbMessage.text = message
|
||||
this.fbMessage.visibility = VISIBLE
|
||||
}
|
||||
|
||||
fun setMessageSpanned(message: Spanned?) {
|
||||
if (message == null) return
|
||||
|
||||
this.fbMessage.text = message
|
||||
this.fbMessage.visibility = VISIBLE
|
||||
}
|
||||
|
||||
fun setMessageTypeface(typeface: Typeface?) {
|
||||
if (typeface == null) return
|
||||
this.fbMessage.typeface = typeface
|
||||
}
|
||||
|
||||
fun setMessageSizeInPx(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbMessage.setTextSize(TypedValue.COMPLEX_UNIT_PX, size)
|
||||
}
|
||||
|
||||
fun setMessageSizeInSp(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbMessage.setTextSize(TypedValue.COMPLEX_UNIT_SP, size)
|
||||
}
|
||||
|
||||
fun setMessageColor(color: Int?) {
|
||||
if (color == null) return
|
||||
this.fbMessage.setTextColor(color)
|
||||
}
|
||||
|
||||
fun setMessageAppearance(messageAppearance: Int?) {
|
||||
if (messageAppearance == null) return
|
||||
this.fbMessage.setTextAppearance(messageAppearance)
|
||||
}
|
||||
|
||||
fun setPrimaryActionText(text: String?) {
|
||||
if (TextUtils.isEmpty(text)) return
|
||||
|
||||
this.fbPrimaryAction.text = text
|
||||
this.fbPrimaryAction.visibility = VISIBLE
|
||||
}
|
||||
|
||||
fun setPrimaryActionTextSpanned(text: Spanned?) {
|
||||
if (text == null) return
|
||||
|
||||
this.fbPrimaryAction.text = text
|
||||
this.fbPrimaryAction.visibility = VISIBLE
|
||||
}
|
||||
|
||||
fun setPrimaryActionTextTypeface(typeface: Typeface?) {
|
||||
if (typeface == null) return
|
||||
this.fbPrimaryAction.typeface = typeface
|
||||
}
|
||||
|
||||
fun setPrimaryActionTextSizeInPx(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbPrimaryAction.setTextSize(TypedValue.COMPLEX_UNIT_PX, size)
|
||||
}
|
||||
|
||||
fun setPrimaryActionTextSizeInSp(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbPrimaryAction.setTextSize(TypedValue.COMPLEX_UNIT_SP, size)
|
||||
}
|
||||
|
||||
fun setPrimaryActionTextColor(color: Int?) {
|
||||
if (color == null) return
|
||||
this.fbPrimaryAction.setTextColor(color)
|
||||
}
|
||||
|
||||
fun setPrimaryActionTextAppearance(messageAppearance: Int?) {
|
||||
if (messageAppearance == null) return
|
||||
this.fbPrimaryAction.setTextAppearance(messageAppearance)
|
||||
}
|
||||
|
||||
fun setPrimaryActionTapListener(listener: OnActionTapListener?) {
|
||||
if (listener == null) return
|
||||
|
||||
this.fbPrimaryAction.setOnClickListener {
|
||||
listener.onActionTapped(parentFlashbarContainer.parentFlashbar)
|
||||
}
|
||||
}
|
||||
|
||||
fun setPositiveActionText(text: String?) {
|
||||
if (TextUtils.isEmpty(text)) return
|
||||
|
||||
this.fbSecondaryActionContainer.visibility = VISIBLE
|
||||
this.fbPositiveAction.text = text
|
||||
this.fbPositiveAction.visibility = VISIBLE
|
||||
}
|
||||
|
||||
fun setPositiveActionTextSpanned(text: Spanned?) {
|
||||
if (text == null) return
|
||||
|
||||
this.fbSecondaryActionContainer.visibility = VISIBLE
|
||||
this.fbPositiveAction.text = text
|
||||
this.fbPositiveAction.visibility = VISIBLE
|
||||
}
|
||||
|
||||
fun setPositiveActionTextTypeface(typeface: Typeface?) {
|
||||
if (typeface == null) return
|
||||
this.fbPositiveAction.typeface = typeface
|
||||
}
|
||||
|
||||
fun setPositiveActionTextSizeInPx(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbPositiveAction.setTextSize(TypedValue.COMPLEX_UNIT_PX, size)
|
||||
}
|
||||
|
||||
fun setPositiveActionTextSizeInSp(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbPositiveAction.setTextSize(TypedValue.COMPLEX_UNIT_SP, size)
|
||||
}
|
||||
|
||||
fun setPositiveActionTextColor(color: Int?) {
|
||||
if (color == null) return
|
||||
this.fbPositiveAction.setTextColor(color)
|
||||
}
|
||||
|
||||
fun setPositiveActionTextAppearance(messageAppearance: Int?) {
|
||||
if (messageAppearance == null) return
|
||||
this.fbPositiveAction.setTextAppearance(messageAppearance)
|
||||
}
|
||||
|
||||
fun setPositiveActionTapListener(listener: OnActionTapListener?) {
|
||||
if (listener == null) return
|
||||
|
||||
this.fbPositiveAction.setOnClickListener {
|
||||
listener.onActionTapped(parentFlashbarContainer.parentFlashbar)
|
||||
}
|
||||
}
|
||||
|
||||
fun setNegativeActionText(text: String?) {
|
||||
if (TextUtils.isEmpty(text)) return
|
||||
|
||||
this.fbSecondaryActionContainer.visibility = VISIBLE
|
||||
this.fbNegativeAction.text = text
|
||||
this.fbNegativeAction.visibility = VISIBLE
|
||||
}
|
||||
|
||||
fun setNegativeActionTextSpanned(text: Spanned?) {
|
||||
if (text == null) return
|
||||
|
||||
this.fbSecondaryActionContainer.visibility = VISIBLE
|
||||
this.fbNegativeAction.text = text
|
||||
this.fbNegativeAction.visibility = VISIBLE
|
||||
}
|
||||
|
||||
fun setNegativeActionTextTypeface(typeface: Typeface?) {
|
||||
if (typeface == null) return
|
||||
this.fbNegativeAction.typeface = typeface
|
||||
}
|
||||
|
||||
fun setNegativeActionTextSizeInPx(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbNegativeAction.setTextSize(TypedValue.COMPLEX_UNIT_PX, size)
|
||||
}
|
||||
|
||||
fun setNegativeActionTextSizeInSp(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbNegativeAction.setTextSize(TypedValue.COMPLEX_UNIT_SP, size)
|
||||
}
|
||||
|
||||
fun setNegativeActionTextColor(color: Int?) {
|
||||
if (color == null) return
|
||||
this.fbNegativeAction.setTextColor(color)
|
||||
}
|
||||
|
||||
fun setNegativeActionTextAppearance(messageAppearance: Int?) {
|
||||
if (messageAppearance == null) return
|
||||
this.fbNegativeAction.setTextAppearance(messageAppearance)
|
||||
}
|
||||
|
||||
fun setNegativeActionTapListener(listener: OnActionTapListener?) {
|
||||
if (listener == null) return
|
||||
|
||||
this.fbNegativeAction.setOnClickListener {
|
||||
listener.onActionTapped(parentFlashbarContainer.parentFlashbar)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun showIcon(showIcon: Boolean) {
|
||||
this.fbIcon.visibility = if (showIcon) VISIBLE else GONE
|
||||
}
|
||||
|
||||
internal fun showIconScale(scale: Float, scaleType: ImageView.ScaleType?) {
|
||||
this.fbIcon.scaleX = scale
|
||||
this.fbIcon.scaleY = scale
|
||||
this.fbIcon.scaleType = scaleType
|
||||
}
|
||||
|
||||
fun setIconDrawable(icon: Drawable?) {
|
||||
if (icon == null) return
|
||||
this.fbIcon.setImageDrawable(icon)
|
||||
}
|
||||
|
||||
fun setIconBitmap(bitmap: Bitmap?) {
|
||||
if (bitmap == null) return
|
||||
this.fbIcon.setImageBitmap(bitmap)
|
||||
}
|
||||
|
||||
fun setIconColorFilter(colorFilter: Int?, filterMode: PorterDuff.Mode?) {
|
||||
if (colorFilter == null) return
|
||||
if (filterMode == null) {
|
||||
this.fbIcon.setColorFilter(colorFilter)
|
||||
} else {
|
||||
this.fbIcon.setColorFilter(colorFilter, filterMode)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun startIconAnimation(animator: FlashAnimIconBuilder?) {
|
||||
animator?.withView(fbIcon)?.build()?.start()
|
||||
}
|
||||
|
||||
internal fun stopIconAnimation() {
|
||||
fbIcon.clearAnimation()
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
internal fun enableSwipeToDismiss(enable: Boolean, callbacks: DismissCallbacks) {
|
||||
if (enable) {
|
||||
fbRoot.setOnTouchListener(SwipeDismissTouchListener(this, callbacks))
|
||||
}
|
||||
}
|
||||
|
||||
fun setProgressPosition(position: ProgressPosition?) {
|
||||
if (position == null) return
|
||||
when (position) {
|
||||
LEFT -> {
|
||||
fbLeftProgress.visibility = VISIBLE
|
||||
fbRightProgress.visibility = GONE
|
||||
}
|
||||
|
||||
RIGHT -> {
|
||||
fbLeftProgress.visibility = GONE
|
||||
fbRightProgress.visibility = VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setProgressTint(progressTint: Int?, position: ProgressPosition?) {
|
||||
if (position == null || progressTint == null) return
|
||||
|
||||
val progressBar =
|
||||
when (position) {
|
||||
LEFT -> fbLeftProgress
|
||||
RIGHT -> fbRightProgress
|
||||
}
|
||||
|
||||
progressBar.progressTintList = ColorStateList.valueOf(progressTint)
|
||||
}
|
||||
}
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.flashbar
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.ValueAnimator
|
||||
import android.view.MotionEvent
|
||||
import android.view.VelocityTracker
|
||||
import android.view.View
|
||||
import android.view.ViewConfiguration
|
||||
import kotlin.math.abs
|
||||
|
||||
internal class SwipeDismissTouchListener(
|
||||
private val view: View,
|
||||
private val callbacks: DismissCallbacks
|
||||
) : View.OnTouchListener {
|
||||
|
||||
private val slop: Int
|
||||
private val minFlingVelocity: Int
|
||||
private val animationTime: Long
|
||||
private var viewWidth = 1
|
||||
|
||||
private var downX: Float = 0.toFloat()
|
||||
private var downY: Float = 0.toFloat()
|
||||
private var swiping: Boolean = false
|
||||
private var swipingSlop: Int = 0
|
||||
private var velocityTracker: VelocityTracker? = null
|
||||
private var translationX: Float = 0.toFloat()
|
||||
|
||||
init {
|
||||
val vc = ViewConfiguration.get(view.context)
|
||||
slop = vc.scaledTouchSlop
|
||||
minFlingVelocity = vc.scaledMinimumFlingVelocity * 16
|
||||
animationTime =
|
||||
view.context.resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
|
||||
}
|
||||
|
||||
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
|
||||
motionEvent.offsetLocation(translationX, 0f)
|
||||
|
||||
if (viewWidth < 2) {
|
||||
viewWidth = this.view.width
|
||||
}
|
||||
|
||||
when (motionEvent.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
downX = motionEvent.rawX
|
||||
downY = motionEvent.rawY
|
||||
velocityTracker = VelocityTracker.obtain()
|
||||
velocityTracker!!.addMovement(motionEvent)
|
||||
return false
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
if (velocityTracker != null) {
|
||||
val deltaX = motionEvent.rawX - downX
|
||||
velocityTracker!!.addMovement(motionEvent)
|
||||
velocityTracker!!.computeCurrentVelocity(1000)
|
||||
val velocityX = velocityTracker!!.xVelocity
|
||||
val absVelocityX = abs(velocityX)
|
||||
val absVelocityY = abs(velocityTracker!!.yVelocity)
|
||||
var dismiss = false
|
||||
var dismissRight = false
|
||||
if (abs(deltaX) > viewWidth / 2 && swiping) {
|
||||
dismiss = true
|
||||
dismissRight = deltaX > 0
|
||||
} else if (minFlingVelocity <= absVelocityX && absVelocityY < absVelocityX && swiping) {
|
||||
dismiss = velocityX < 0 == deltaX < 0
|
||||
dismissRight = velocityTracker!!.xVelocity > 0
|
||||
}
|
||||
if (dismiss) {
|
||||
this.view
|
||||
.animate()
|
||||
.translationX(if (dismissRight) viewWidth.toFloat() else -viewWidth.toFloat())
|
||||
.alpha(0f)
|
||||
.setDuration(animationTime)
|
||||
.setListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
performDismiss()
|
||||
}
|
||||
}
|
||||
)
|
||||
} else if (swiping) {
|
||||
this.view
|
||||
.animate()
|
||||
.translationX(0f)
|
||||
.alpha(1f)
|
||||
.setDuration(animationTime)
|
||||
.setListener(null)
|
||||
}
|
||||
velocityTracker!!.recycle()
|
||||
velocityTracker = null
|
||||
translationX = 0f
|
||||
downX = 0f
|
||||
downY = 0f
|
||||
swiping = false
|
||||
callbacks.onSwipe(false)
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
if (velocityTracker != null) {
|
||||
this.view
|
||||
.animate()
|
||||
.translationX(0f)
|
||||
.alpha(1f)
|
||||
.setDuration(animationTime)
|
||||
.setListener(null)
|
||||
velocityTracker!!.recycle()
|
||||
velocityTracker = null
|
||||
translationX = 0f
|
||||
downX = 0f
|
||||
downY = 0f
|
||||
swiping = false
|
||||
callbacks.onSwipe(false)
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (velocityTracker != null) {
|
||||
velocityTracker!!.addMovement(motionEvent)
|
||||
val deltaX = motionEvent.rawX - downX
|
||||
val deltaY = motionEvent.rawY - downY
|
||||
if (abs(deltaX) > slop && abs(deltaY) < abs(deltaX) / 2) {
|
||||
swiping = true
|
||||
callbacks.onSwipe(true)
|
||||
swipingSlop = if (deltaX > 0) slop else -slop
|
||||
this.view.parent.requestDisallowInterceptTouchEvent(true)
|
||||
|
||||
val cancelEvent = MotionEvent.obtain(motionEvent)
|
||||
cancelEvent.action =
|
||||
MotionEvent.ACTION_CANCEL or
|
||||
(motionEvent.actionIndex shl MotionEvent.ACTION_POINTER_INDEX_SHIFT)
|
||||
this.view.onTouchEvent(cancelEvent)
|
||||
cancelEvent.recycle()
|
||||
}
|
||||
|
||||
if (swiping) {
|
||||
translationX = deltaX
|
||||
this.view.translationX = deltaX - swipingSlop
|
||||
this.view.alpha = 0f.coerceAtLeast(1f.coerceAtMost(1f - 2f * abs(deltaX) / viewWidth))
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
view.performClick()
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun performDismiss() {
|
||||
val lp = view.layoutParams
|
||||
val originalHeight = view.height
|
||||
|
||||
val animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(animationTime)
|
||||
|
||||
animator.addListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
callbacks.onDismiss(view)
|
||||
view.alpha = 1f
|
||||
view.translationX = 0f
|
||||
lp.height = originalHeight
|
||||
view.layoutParams = lp
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
animator.addUpdateListener { valueAnimator ->
|
||||
lp.height = valueAnimator.animatedValue as Int
|
||||
view.layoutParams = lp
|
||||
}
|
||||
|
||||
animator.start()
|
||||
}
|
||||
|
||||
internal interface DismissCallbacks {
|
||||
|
||||
fun onSwipe(isSwiping: Boolean)
|
||||
|
||||
fun onDismiss(view: View)
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package com.itsaky.androidide.flashbar.anim
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.view.animation.AnimationUtils
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.animation.Interpolator
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.annotation.InterpolatorRes
|
||||
import com.itsaky.androidide.flashbar.R
|
||||
|
||||
abstract class BaseFlashAnimBuilder(private val context: Context) {
|
||||
|
||||
private val DEFAULT_DURATION =
|
||||
context.resources.getInteger(R.integer.default_animation_duration).toLong()
|
||||
|
||||
protected val DEFAULT_ALPHA_START = 0.2f
|
||||
protected val DEFAULT_ALPHA_END = 1.0f
|
||||
|
||||
protected var view: View? = null
|
||||
protected var duration = DEFAULT_DURATION
|
||||
protected var interpolator: Interpolator? = null
|
||||
|
||||
protected var alpha: Boolean = false
|
||||
|
||||
@CallSuper
|
||||
open fun duration(millis: Long) = apply {
|
||||
require(duration >= 0) { "Duration must not be negative" }
|
||||
this.duration = millis
|
||||
}
|
||||
|
||||
@CallSuper open fun accelerate() = apply { this.interpolator = AccelerateInterpolator() }
|
||||
|
||||
@CallSuper open fun decelerate() = apply { this.interpolator = DecelerateInterpolator() }
|
||||
|
||||
@CallSuper
|
||||
open fun accelerateDecelerate() = apply { this.interpolator = AccelerateDecelerateInterpolator() }
|
||||
|
||||
@CallSuper
|
||||
open fun interpolator(interpolator: Interpolator) = apply { this.interpolator = interpolator }
|
||||
|
||||
@CallSuper
|
||||
open fun interpolator(@InterpolatorRes id: Int) = apply {
|
||||
this.interpolator = AnimationUtils.loadInterpolator(context, id)
|
||||
}
|
||||
|
||||
@CallSuper open fun alpha() = apply { this.alpha = true }
|
||||
|
||||
@CallSuper internal open fun withView(view: View) = apply { this.view = view }
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.itsaky.androidide.flashbar.anim
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Context
|
||||
|
||||
class FlashAnim(private val compositeAnim: AnimatorSet) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun with(context: Context) = FlashAnimRetriever(context)
|
||||
}
|
||||
|
||||
internal fun start(listener: InternalAnimListener? = null) {
|
||||
if (listener != null) {
|
||||
val primaryAnim = compositeAnim.childAnimations[0] as ObjectAnimator
|
||||
|
||||
primaryAnim.addListener(
|
||||
object : Animator.AnimatorListener {
|
||||
override fun onAnimationRepeat(animator: Animator) {}
|
||||
|
||||
override fun onAnimationEnd(animator: Animator) {
|
||||
listener.onStop()
|
||||
|
||||
primaryAnim.removeAllListeners()
|
||||
primaryAnim.removeAllUpdateListeners()
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animator: Animator) {}
|
||||
|
||||
override fun onAnimationStart(animator: Animator) {
|
||||
listener.onStart()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
primaryAnim.addUpdateListener { listener.onUpdate(it.animatedFraction) }
|
||||
}
|
||||
|
||||
compositeAnim.start()
|
||||
}
|
||||
|
||||
internal interface InternalAnimListener {
|
||||
fun onStart()
|
||||
fun onUpdate(progress: Float)
|
||||
fun onStop()
|
||||
}
|
||||
}
|
||||
|
||||
class FlashAnimRetriever(private val context: Context) {
|
||||
/** Retrieves the builder for animating flashbar */
|
||||
fun animateBar() = FlashAnimBarBuilder(context)
|
||||
|
||||
/** Retrieves the builder for animating the icon in the flashbar */
|
||||
fun animateIcon() = FlashAnimIconBuilder(context)
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package com.itsaky.androidide.flashbar.anim
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.animation.AnticipateInterpolator
|
||||
import android.view.animation.Interpolator
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.InterpolatorRes
|
||||
import com.itsaky.androidide.flashbar.Flashbar
|
||||
import com.itsaky.androidide.flashbar.Flashbar.Gravity.BOTTOM
|
||||
import com.itsaky.androidide.flashbar.Flashbar.Gravity.TOP
|
||||
import com.itsaky.androidide.flashbar.anim.FlashAnimBarBuilder.Direction.LEFT
|
||||
import com.itsaky.androidide.flashbar.anim.FlashAnimBarBuilder.Direction.RIGHT
|
||||
import com.itsaky.androidide.flashbar.anim.FlashAnimBarBuilder.Type.ENTER
|
||||
import com.itsaky.androidide.flashbar.anim.FlashAnimBarBuilder.Type.EXIT
|
||||
|
||||
class FlashAnimBarBuilder(context: Context) : BaseFlashAnimBuilder(context) {
|
||||
|
||||
private var type: Type? = null
|
||||
private var gravity: Flashbar.Gravity? = null
|
||||
private var direction: Direction? = null
|
||||
|
||||
/** Specifies the duration (in millis) for the animation */
|
||||
override fun duration(millis: Long) = apply { super.duration(millis) }
|
||||
|
||||
/** Specifies accelerate interpolator for the animation */
|
||||
override fun accelerate() = apply { super.accelerate() }
|
||||
|
||||
/** Specifies decelerate interpolator for the animation */
|
||||
override fun decelerate() = apply { super.decelerate() }
|
||||
|
||||
/** Specifies accelerate-decelerate interpolator for the animation */
|
||||
override fun accelerateDecelerate() = apply { super.accelerateDecelerate() }
|
||||
|
||||
/** Specifies custom interpolator for the animation */
|
||||
override fun interpolator(interpolator: Interpolator) = apply { super.interpolator(interpolator) }
|
||||
|
||||
/** Specifies custom interpolator resource for the animation */
|
||||
override fun interpolator(@InterpolatorRes id: Int) = apply { super.interpolator(id) }
|
||||
|
||||
/** Specifies that the animation should have alpha effect */
|
||||
override fun alpha() = apply { super.alpha() }
|
||||
|
||||
/** Specifies that the bar should slide to/from the left */
|
||||
fun slideFromLeft() = apply { this.direction = LEFT }
|
||||
|
||||
/** Specifies that the bar should slide to/from the right */
|
||||
fun slideFromRight() = apply { this.direction = RIGHT }
|
||||
|
||||
/** Specifies overshoot interpolator for the animation */
|
||||
fun overshoot() = apply { this.interpolator = OvershootInterpolator() }
|
||||
|
||||
/** Specifies overshoot-anticipate interpolator for the animation */
|
||||
fun anticipateOvershoot() = apply { this.interpolator = AnticipateInterpolator() }
|
||||
|
||||
override fun withView(view: View) = apply { super.withView(view) }
|
||||
|
||||
internal fun enter() = apply { this.type = ENTER }
|
||||
|
||||
internal fun exit() = apply { this.type = EXIT }
|
||||
|
||||
internal fun fromTop() = apply { this.gravity = TOP }
|
||||
|
||||
internal fun fromBottom() = apply { this.gravity = BOTTOM }
|
||||
|
||||
internal fun build(): FlashAnim {
|
||||
requireNotNull(view, { "Target view can not be null" })
|
||||
|
||||
val compositeAnim = AnimatorSet()
|
||||
val animators = linkedSetOf<Animator>()
|
||||
|
||||
val translationAnim = ObjectAnimator()
|
||||
// Slide from left/right animation is not specified, default top/bottom
|
||||
// animation is applied
|
||||
if (direction == null) {
|
||||
translationAnim.setPropertyName("translationY")
|
||||
|
||||
when (type!!) {
|
||||
ENTER ->
|
||||
when (gravity!!) {
|
||||
TOP -> translationAnim.setFloatValues(-view!!.height.toFloat(), 0f)
|
||||
BOTTOM -> translationAnim.setFloatValues(view!!.height.toFloat(), 0f)
|
||||
}
|
||||
EXIT ->
|
||||
when (gravity!!) {
|
||||
TOP -> translationAnim.setFloatValues(0f, -view!!.height.toFloat())
|
||||
BOTTOM -> translationAnim.setFloatValues(0f, view!!.height.toFloat())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
translationAnim.setPropertyName("translationX")
|
||||
|
||||
when (type!!) {
|
||||
ENTER ->
|
||||
when (direction!!) {
|
||||
LEFT -> translationAnim.setFloatValues(-view!!.width.toFloat(), 0f)
|
||||
RIGHT -> translationAnim.setFloatValues(view!!.width.toFloat(), 0f)
|
||||
}
|
||||
EXIT ->
|
||||
when (direction!!) {
|
||||
LEFT -> translationAnim.setFloatValues(0f, -view!!.width.toFloat())
|
||||
RIGHT -> translationAnim.setFloatValues(0f, view!!.width.toFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
translationAnim.target = view
|
||||
animators.add(translationAnim)
|
||||
|
||||
if (alpha) {
|
||||
val alphaAnim = ObjectAnimator()
|
||||
alphaAnim.setPropertyName("alpha")
|
||||
alphaAnim.target = view
|
||||
|
||||
when (type!!) {
|
||||
ENTER -> alphaAnim.setFloatValues(DEFAULT_ALPHA_START, DEFAULT_ALPHA_END)
|
||||
EXIT -> alphaAnim.setFloatValues(DEFAULT_ALPHA_END, DEFAULT_ALPHA_START)
|
||||
}
|
||||
animators.add(alphaAnim)
|
||||
}
|
||||
|
||||
compositeAnim.playTogether(animators)
|
||||
compositeAnim.duration = duration
|
||||
compositeAnim.interpolator = interpolator
|
||||
|
||||
return FlashAnim(compositeAnim)
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
ENTER,
|
||||
EXIT
|
||||
}
|
||||
enum class Direction {
|
||||
LEFT,
|
||||
RIGHT
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package com.itsaky.androidide.flashbar.anim
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.animation.PropertyValuesHolder
|
||||
import android.animation.ValueAnimator.INFINITE
|
||||
import android.animation.ValueAnimator.REVERSE
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.animation.Interpolator
|
||||
import androidx.annotation.InterpolatorRes
|
||||
|
||||
class FlashAnimIconBuilder(context: Context) : BaseFlashAnimBuilder(context) {
|
||||
|
||||
private val DEFAULT_PULSE_START = 1.0f
|
||||
private val DEFAULT_PULSE_END = 0.6f
|
||||
|
||||
private var pulse: Boolean = false
|
||||
private var pulseStart = 0f
|
||||
private var pulseEnd = 0f
|
||||
|
||||
/** Specifies the duration (in millis) for the animation */
|
||||
override fun duration(millis: Long) = apply { super.duration(millis) }
|
||||
|
||||
/** Specifies accelerate interpolator for the animation */
|
||||
override fun accelerate() = apply { super.accelerate() }
|
||||
|
||||
/** Specifies decelerate interpolator for the animation */
|
||||
override fun decelerate() = apply { super.decelerate() }
|
||||
|
||||
/** Specifies accelerate-decelerate interpolator for the animation */
|
||||
override fun accelerateDecelerate() = apply { super.accelerateDecelerate() }
|
||||
|
||||
/** Specifies custom interpolator for the animation */
|
||||
override fun interpolator(interpolator: Interpolator) = apply { super.interpolator(interpolator) }
|
||||
|
||||
/** Specifies custom interpolator resource for the animation */
|
||||
override fun interpolator(@InterpolatorRes id: Int) = apply { super.interpolator(id) }
|
||||
|
||||
/** Specifies that the animation should have alpha effect */
|
||||
override fun alpha() = apply { super.alpha() }
|
||||
|
||||
/** Specifies that the animation should have pulse effect */
|
||||
@JvmOverloads
|
||||
fun pulse(start: Float = DEFAULT_PULSE_START, end: Float = DEFAULT_PULSE_END) = apply {
|
||||
this.pulse = true
|
||||
this.pulseStart = start
|
||||
this.pulseEnd = end
|
||||
}
|
||||
|
||||
override fun withView(view: View) = apply { super.withView(view) }
|
||||
|
||||
internal fun build(): FlashAnim {
|
||||
requireNotNull(view, { "Target view can not be null" })
|
||||
|
||||
val compositeAnim = AnimatorSet()
|
||||
val animators = linkedSetOf<Animator>()
|
||||
|
||||
if (pulse) {
|
||||
val scaleAnim =
|
||||
ObjectAnimator.ofPropertyValuesHolder(
|
||||
view,
|
||||
PropertyValuesHolder.ofFloat("scaleX", pulseStart, pulseEnd),
|
||||
PropertyValuesHolder.ofFloat("scaleY", pulseStart, pulseEnd)
|
||||
)
|
||||
|
||||
scaleAnim.repeatCount = INFINITE
|
||||
scaleAnim.repeatMode = REVERSE
|
||||
|
||||
animators.add(scaleAnim)
|
||||
}
|
||||
|
||||
if (alpha) {
|
||||
val alpha = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0.6f)
|
||||
|
||||
alpha.repeatCount = INFINITE
|
||||
alpha.repeatMode = REVERSE
|
||||
|
||||
animators.add(alpha)
|
||||
}
|
||||
|
||||
compositeAnim.playTogether(animators)
|
||||
|
||||
compositeAnim.duration = duration
|
||||
compositeAnim.interpolator = interpolator
|
||||
|
||||
return FlashAnim(compositeAnim)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.itsaky.androidide.flashbar.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import android.os.Build
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.Surface.ROTATION_0
|
||||
import android.view.Surface.ROTATION_180
|
||||
import android.view.Surface.ROTATION_270
|
||||
import android.view.Surface.ROTATION_90
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import com.itsaky.androidide.flashbar.util.NavigationBarPosition.BOTTOM
|
||||
import com.itsaky.androidide.flashbar.util.NavigationBarPosition.LEFT
|
||||
import com.itsaky.androidide.flashbar.util.NavigationBarPosition.RIGHT
|
||||
import com.itsaky.androidide.flashbar.util.NavigationBarPosition.TOP
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
internal fun Activity.getStatusBarHeightInPx(): Int {
|
||||
val rectangle = Rect()
|
||||
|
||||
window.decorView.getWindowVisibleDisplayFrame(rectangle)
|
||||
|
||||
val statusBarHeight = rectangle.top
|
||||
val contentViewTop = window.findViewById<View>(Window.ID_ANDROID_CONTENT).top
|
||||
return if (contentViewTop == 0) { // Actionbar is not present
|
||||
statusBarHeight
|
||||
} else {
|
||||
contentViewTop - statusBarHeight
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Activity.getNavigationBarPosition(): NavigationBarPosition {
|
||||
val display =
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
display
|
||||
} else {
|
||||
@Suppress("DEPRECATION") windowManager.defaultDisplay
|
||||
}
|
||||
return when (display?.rotation) {
|
||||
ROTATION_0 -> BOTTOM
|
||||
ROTATION_90 -> RIGHT
|
||||
ROTATION_180 -> TOP
|
||||
ROTATION_270 -> LEFT
|
||||
else -> BOTTOM
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Activity.getNavigationBarSizeInPx(): Int {
|
||||
val realScreenSize = getRealScreenSize()
|
||||
val appUsableScreenSize = getAppUsableScreenSize()
|
||||
val navigationBarPosition = getNavigationBarPosition()
|
||||
|
||||
return if (navigationBarPosition == LEFT || navigationBarPosition == RIGHT) {
|
||||
realScreenSize.x - appUsableScreenSize.x
|
||||
} else {
|
||||
realScreenSize.y - appUsableScreenSize.y
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Activity?.getRootView(): ViewGroup? {
|
||||
if (this == null || window == null) {
|
||||
return null
|
||||
}
|
||||
return window.decorView as ViewGroup
|
||||
}
|
||||
|
||||
internal fun Context.convertDpToPx(dp: Int): Int {
|
||||
return (dp * (resources.displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT)).roundToInt()
|
||||
}
|
||||
|
||||
internal fun Context.convertPxToDp(px: Int): Int {
|
||||
return (px / (resources.displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT)).roundToInt()
|
||||
}
|
||||
|
||||
private fun Activity.getRealScreenSize(): Point {
|
||||
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
val defaultDisplay =
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
display
|
||||
} else {
|
||||
@Suppress("DEPRECATION") windowManager.defaultDisplay
|
||||
}
|
||||
|
||||
val size = Point()
|
||||
defaultDisplay?.getRealSize(size)
|
||||
return size
|
||||
}
|
||||
|
||||
private fun Activity.getAppUsableScreenSize(): Point {
|
||||
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
val defaultDisplay = windowManager.defaultDisplay
|
||||
val size = Point()
|
||||
defaultDisplay.getSize(size)
|
||||
return size
|
||||
}
|
||||
|
||||
inline fun <T : View> T.afterMeasured(crossinline f: T.() -> Unit) {
|
||||
viewTreeObserver.addOnGlobalLayoutListener(
|
||||
object : ViewTreeObserver.OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
if (measuredWidth > 0 && measuredHeight > 0) {
|
||||
viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
f()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
internal enum class NavigationBarPosition {
|
||||
BOTTOM,
|
||||
RIGHT,
|
||||
LEFT,
|
||||
TOP
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/translucent_black" android:state_pressed="true" />
|
||||
</selector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<gradient
|
||||
android:angle="90"
|
||||
android:endColor="@color/shadow_color"
|
||||
android:startColor="@android:color/transparent" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<gradient
|
||||
android:angle="-90"
|
||||
android:endColor="@color/shadow_color"
|
||||
android:startColor="@android:color/transparent" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,142 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:fitsSystemWindows="true"
|
||||
android:focusable="true"
|
||||
app:cardBackgroundColor="?colorPrimaryContainer"
|
||||
app:cardElevation="@dimen/fb_card_elevation"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Material3.Corner.Bottom">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/fbRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/fb_content_padding_all"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/fbContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/fbLeftProgress"
|
||||
android:layout_width="@dimen/fb_progress_size"
|
||||
android:layout_height="@dimen/fb_progress_size"
|
||||
android:layout_marginEnd="@dimen/fb_margin_progress"
|
||||
android:layout_marginRight="@dimen/fb_margin_progress"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/fbIcon"
|
||||
android:layout_width="@dimen/fb_icon_size"
|
||||
android:layout_height="@dimen/fb_icon_size"
|
||||
android:layout_marginEnd="@dimen/fb_margin_right_icon"
|
||||
android:layout_marginRight="@dimen/fb_margin_right_icon"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/ic_info"
|
||||
android:visibility="gone"
|
||||
app:tint="?colorOnPrimaryContainer"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="8"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fbTitle"
|
||||
android:textStyle="bold"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/fb_title_message_margin"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:textColor="?colorOnPrimaryContainer"
|
||||
android:visibility="gone"
|
||||
tools:text="Title"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fbMessage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/fb_title_message_margin"
|
||||
android:justificationMode="inter_word"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
android:visibility="gone"
|
||||
tools:text="A quick brown fox jumps of the lazy dog."
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/fbPrimaryAction"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAllCaps="false"
|
||||
android:visibility="gone"
|
||||
tools:text="CLICK"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/fbRightProgress"
|
||||
android:layout_width="@dimen/fb_progress_size"
|
||||
android:layout_height="@dimen/fb_progress_size"
|
||||
android:layout_marginStart="@dimen/fb_margin_progress"
|
||||
android:layout_marginLeft="@dimen/fb_margin_progress"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/fbSecondaryActionContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/fb_margin_secondary_action_container"
|
||||
android:gravity="end"
|
||||
android:visibility="gone">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/fbNegativeAction"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
tools:text="CANCEL"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/fbPositiveAction"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/fb_margin_secondary_action"
|
||||
android:layout_marginLeft="@dimen/fb_margin_secondary_action"
|
||||
android:visibility="gone"
|
||||
tools:text="OK"
|
||||
tools:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="FbProgress">
|
||||
<attr name="fbp_progressIndeterminate" format="boolean" />
|
||||
<attr name="fbp_barColor" format="color" />
|
||||
<attr name="fbp_rimColor" format="color" />
|
||||
<attr name="fbp_rimWidth" format="dimension" />
|
||||
<attr name="fbp_spinSpeed" format="float" />
|
||||
<attr name="fbp_barSpinCycleTime" format="integer" />
|
||||
<attr name="fbp_circleRadius" format="dimension" />
|
||||
<attr name="fbp_fillRadius" format="boolean" />
|
||||
<attr name="fbp_barWidth" format="dimension" />
|
||||
<attr name="fbp_linearProgress" format="boolean" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="warm_grey">#8e8e8e</color>
|
||||
<color name="white">#fff</color>
|
||||
<color name="shadow_color">#94444444</color>
|
||||
<color name="translucent_black">#22000000</color>
|
||||
<color name="modal">#80272727</color>
|
||||
<color name="slate_black">#FF333333</color>
|
||||
<color name="yellow">#FFEB3B</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="fb_card_elevation">8dp</dimen>
|
||||
|
||||
<dimen name="fb_content_padding_all">16dp</dimen>
|
||||
<dimen name="fb_title_text_size">16sp</dimen>
|
||||
<dimen name="fb_message_text_size">14sp</dimen>
|
||||
<dimen name="fb_title_message_margin">2dp</dimen>
|
||||
<dimen name="fb_icon_size">28dp</dimen>
|
||||
<dimen name="fb_margin_right_icon">16dp</dimen>
|
||||
<dimen name="fb_button_text_size">16sp</dimen>
|
||||
<dimen name="fb_top_compensation_margin">16dp</dimen>
|
||||
<dimen name="fb_bottom_compensation_margin">30dp</dimen>
|
||||
<dimen name="fb_progress_size">20dp</dimen>
|
||||
<dimen name="fb_margin_secondary_action">12dp</dimen>
|
||||
<dimen name="fb_margin_secondary_action_container">12dp</dimen>
|
||||
<dimen name="fb_margin_progress">20dp</dimen>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="default_animation_duration">250</integer>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user