mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
feat: replace Toaster with Flashbar
This commit is contained in:
Generated
-5
@@ -8,11 +8,6 @@
|
||||
<option name="JD_DO_NOT_WRAP_ONE_LINE_COMMENTS" value="true" />
|
||||
</JavaCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value />
|
||||
</option>
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="IMPORT_NESTED_CLASSES" value="true" />
|
||||
<option name="ALLOW_TRAILING_COMMA" value="true" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.8.0" />
|
||||
</component>
|
||||
</project>
|
||||
+1
-1
@@ -210,7 +210,7 @@
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
@@ -73,6 +73,8 @@ android {
|
||||
kapt { arguments { arg("eventBusIndex", "com.itsaky.androidide.events.AppEventsIndex") } }
|
||||
|
||||
dependencies {
|
||||
|
||||
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.10")
|
||||
|
||||
// Annotation processors
|
||||
kapt(libs.common.glide.ap)
|
||||
|
||||
Binary file not shown.
@@ -19,13 +19,12 @@ package com.itsaky.androidide.actions.file
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.toaster.Toaster
|
||||
import com.itsaky.toaster.toast
|
||||
import com.itsaky.androidide.utils.flashSuccess
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class SaveFileAction() : EditorRelatedAction() {
|
||||
@@ -77,7 +76,7 @@ class SaveFileAction() : EditorRelatedAction() {
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result is Boolean && result) {
|
||||
toast(R.string.all_saved, Toaster.Type.SUCCESS)
|
||||
flashSuccess(R.string.all_saved)
|
||||
} else {
|
||||
log.error("Failed to save file")
|
||||
TODO("Create message in strings.xml")
|
||||
|
||||
@@ -41,9 +41,8 @@ import com.itsaky.androidide.projects.ProjectManager.projectPath
|
||||
import com.itsaky.androidide.resources.R.string
|
||||
import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.Environment
|
||||
import com.itsaky.toaster.Toaster.Type.ERROR
|
||||
import com.itsaky.toaster.Toaster.Type.INFO
|
||||
import com.itsaky.toaster.toast
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import com.itsaky.androidide.utils.flashInfo
|
||||
import java.io.File
|
||||
|
||||
class MainActivity : IDEActivity() {
|
||||
@@ -67,7 +66,7 @@ class MainActivity : IDEActivity() {
|
||||
|
||||
override fun onStorageGranted() {}
|
||||
override fun onStorageDenied() {
|
||||
toast(string.msg_storage_denied, ERROR)
|
||||
flashError(string.msg_storage_denied)
|
||||
finishAffinity()
|
||||
}
|
||||
|
||||
@@ -130,12 +129,12 @@ class MainActivity : IDEActivity() {
|
||||
}
|
||||
if (TextUtils.isEmpty(openedProject)) {
|
||||
app
|
||||
toast(string.msg_opened_project_does_not_exist, INFO)
|
||||
flashInfo(string.msg_opened_project_does_not_exist)
|
||||
return
|
||||
}
|
||||
val project = File(openedProject)
|
||||
if (!project.exists()) {
|
||||
toast(string.msg_opened_project_does_not_exist, INFO)
|
||||
flashInfo(string.msg_opened_project_does_not_exist)
|
||||
return
|
||||
}
|
||||
if (confirmProjectOpen) {
|
||||
|
||||
@@ -90,17 +90,16 @@ import com.itsaky.androidide.utils.DialogUtils.newMaterialDialogBuilder
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.InstallationResultHandler.onResult
|
||||
import com.itsaky.androidide.utils.SingleSessionCallback
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import com.itsaky.androidide.utils.resolveAttr
|
||||
import com.itsaky.androidide.viewmodel.EditorViewModel
|
||||
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
|
||||
import com.itsaky.androidide.xml.versions.ApiVersionsRegistry
|
||||
import com.itsaky.androidide.xml.widgets.WidgetTableRegistry
|
||||
import com.itsaky.toaster.Toaster.Type.ERROR
|
||||
import com.itsaky.toaster.toast
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode.MAIN
|
||||
import java.io.File
|
||||
import java.util.Objects
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode.MAIN
|
||||
|
||||
/**
|
||||
* Base class for EditorActivity which handles most of the view related things.
|
||||
@@ -251,7 +250,7 @@ abstract class BaseEditorActivity :
|
||||
getFileTreeFragment()?.listProjectFiles()
|
||||
} catch (th: Throwable) {
|
||||
log.error("Failed to update files list", th)
|
||||
toast(string.msg_failed_list_files, ERROR)
|
||||
flashError(string.msg_failed_list_files)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,9 +43,7 @@ import com.itsaky.androidide.tasks.executeAsync
|
||||
import com.itsaky.androidide.ui.editor.CodeEditorView
|
||||
import com.itsaky.androidide.utils.DialogUtils.newYesNoDialog
|
||||
import com.itsaky.androidide.utils.IntentUtils.openImage
|
||||
import com.itsaky.androidide.utils.LSPUtils
|
||||
import com.itsaky.toaster.Toaster.Type.SUCCESS
|
||||
import com.itsaky.toaster.toast
|
||||
import com.itsaky.androidide.utils.flashSuccess
|
||||
import io.github.rosemoe.sora.event.ContentChangeEvent
|
||||
import java.io.File
|
||||
|
||||
@@ -107,7 +105,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler {
|
||||
viewModel.openedFilesCache = cache
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
executeAsync { IDEColorSchemeProvider.initIfNeeded() }
|
||||
}
|
||||
|
||||
@@ -184,7 +182,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler {
|
||||
editor.setSelection(0, 0)
|
||||
return@post
|
||||
}
|
||||
|
||||
|
||||
editor.validateRange(selection)
|
||||
editor.setSelection(selection)
|
||||
}
|
||||
@@ -275,7 +273,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler {
|
||||
val result = saveAllResult()
|
||||
|
||||
if (notify) {
|
||||
toast(string.all_saved, SUCCESS)
|
||||
flashSuccess(string.all_saved)
|
||||
}
|
||||
|
||||
if (result.gradleSaved) {
|
||||
|
||||
+15
-15
@@ -51,8 +51,7 @@ import com.itsaky.androidide.services.GradleBuildServiceConnnection
|
||||
import com.itsaky.androidide.tooling.api.messages.result.InitializeResult
|
||||
import com.itsaky.androidide.utils.DialogUtils.newMaterialDialogBuilder
|
||||
import com.itsaky.androidide.utils.RecursiveFileSearcher
|
||||
import com.itsaky.toaster.Toaster.Type.ERROR
|
||||
import com.itsaky.toaster.toast
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import java.io.File
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.regex.Pattern
|
||||
@@ -64,7 +63,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
|
||||
|
||||
protected var mSearchingProgress: ProgressSheet? = null
|
||||
protected var mFindInProjectDialog: AlertDialog? = null
|
||||
|
||||
|
||||
protected var isFromSavedInstance = false
|
||||
protected var shouldInitialize = false
|
||||
|
||||
@@ -98,18 +97,19 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
||||
savedInstanceState?.let {
|
||||
this.shouldInitialize = it.getBoolean(STATE_KEY_SHOULD_INITIALIZE, true)
|
||||
this.isFromSavedInstance = it.getBoolean(STATE_KEY_FROM_SAVED_INSTANACE, false)
|
||||
} ?: run {
|
||||
this.shouldInitialize = true
|
||||
this.isFromSavedInstance = false
|
||||
}
|
||||
|
||||
?: run {
|
||||
this.shouldInitialize = true
|
||||
this.isFromSavedInstance = false
|
||||
}
|
||||
|
||||
startServices()
|
||||
}
|
||||
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.apply {
|
||||
@@ -125,7 +125,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
|
||||
// sometimes, when the IDE closed and reopened instantly, these values prevent initialization
|
||||
// of the project
|
||||
ProjectManager.destroy()
|
||||
|
||||
|
||||
viewModel.isInitializing = false
|
||||
viewModel.isBuildInProgress = false
|
||||
}
|
||||
@@ -297,7 +297,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
|
||||
protected open fun createFindInProjectDialog(): AlertDialog? {
|
||||
if (rootProject == null) {
|
||||
log.warn("No root project model found. Is the project initialized?")
|
||||
toast(getString(string.msg_project_not_initialized), ERROR)
|
||||
flashError(getString(string.msg_project_not_initialized))
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
|
||||
try {
|
||||
rootProject!!.subModules.stream().map(Project::projectDir).collect(Collectors.toList())
|
||||
} catch (e: Throwable) {
|
||||
toast(getString(string.msg_no_modules), ERROR)
|
||||
flashError(getString(string.msg_no_modules))
|
||||
emptyList()
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
|
||||
builder.setPositiveButton(string.menu_find) { dialog, _ ->
|
||||
val text = binding.input.editText!!.text.toString().trim()
|
||||
if (text.isEmpty()) {
|
||||
toast(string.msg_empty_search_query, ERROR)
|
||||
flashError(string.msg_empty_search_query)
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
|
||||
}
|
||||
|
||||
if (searchDirs.isEmpty()) {
|
||||
toast(string.msg_select_search_modules, ERROR)
|
||||
flashError(string.msg_select_search_modules)
|
||||
} else {
|
||||
dialog.dismiss()
|
||||
|
||||
@@ -447,7 +447,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
|
||||
|
||||
open fun getProgressSheet(msg: Int): ProgressSheet? {
|
||||
doDismissSearchProgress()
|
||||
|
||||
|
||||
mSearchingProgress =
|
||||
ProgressSheet().also {
|
||||
it.isCancelable = false
|
||||
|
||||
@@ -35,12 +35,11 @@ import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE
|
||||
import com.itsaky.androidide.tasks.executeAsync
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.VMUtils
|
||||
import com.itsaky.toaster.Toaster.Type.ERROR
|
||||
import com.itsaky.toaster.toast
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import io.github.rosemoe.sora.widget.schemes.EditorColorScheme
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.lang.Thread.UncaughtExceptionHandler
|
||||
import kotlin.system.exitProcess
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
class IDEApplication : BaseApplication() {
|
||||
|
||||
@@ -107,7 +106,7 @@ class IDEApplication : BaseApplication() {
|
||||
startActivity(intent)
|
||||
} catch (th: Throwable) {
|
||||
LOG.error("Unable to start activity to show changelog", th)
|
||||
toast("Unable to start activity", ERROR)
|
||||
flashError("Unable to start activity")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import com.blankj.utilcode.util.ThreadUtils
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.itsaky.androidide.activities.editor.EditorActivityKt
|
||||
import com.itsaky.androidide.activities.PreferencesActivity
|
||||
import com.itsaky.androidide.activities.TerminalActivity
|
||||
import com.itsaky.androidide.activities.editor.EditorActivityKt
|
||||
import com.itsaky.androidide.adapters.MainActionsListAdapter
|
||||
import com.itsaky.androidide.app.BaseApplication
|
||||
import com.itsaky.androidide.common.databinding.LayoutDialogProgressBinding
|
||||
@@ -25,11 +25,11 @@ import com.itsaky.androidide.tasks.executeAsyncProvideError
|
||||
import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.Environment
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.toaster.toastError
|
||||
import com.itsaky.toaster.toastSuccess
|
||||
import java.io.File
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import com.itsaky.androidide.utils.flashSuccess
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.ProgressMonitor
|
||||
import java.io.File
|
||||
|
||||
class MainFragment : BaseFragment(), OnProjectCreatedListener {
|
||||
private var binding: FragmentMainBinding? = null
|
||||
@@ -179,14 +179,14 @@ class MainFragment : BaseFragment(), OnProjectCreatedListener {
|
||||
if (!future.isCancelled) {
|
||||
showCloneError(error)
|
||||
}
|
||||
} else toastSuccess(string.git_clone_success)
|
||||
} else flashSuccess(string.git_clone_success)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCloneError(error: Throwable?) {
|
||||
if (error == null) {
|
||||
toastError(string.git_clone_failed)
|
||||
flashError(string.git_clone_failed)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -51,9 +51,9 @@ import com.itsaky.androidide.tooling.api.model.GradleTask
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.SingleTextWatcher
|
||||
import com.itsaky.androidide.utils.doOnApplyWindowInsets
|
||||
import com.itsaky.androidide.utils.flashInfo
|
||||
import com.itsaky.androidide.utils.updateSystemBarColors
|
||||
import com.itsaky.androidide.viewmodel.RunTasksViewModel
|
||||
import com.itsaky.toaster.toastInfo
|
||||
|
||||
/**
|
||||
* A bottom sheet dialog fragment to show UI which allows the users to select and execute Gradle
|
||||
@@ -143,7 +143,7 @@ class RunTasksDialogFragment : BottomSheetDialogFragment() {
|
||||
|
||||
binding.exec.setOnClickListener {
|
||||
if (viewModel.selected.isEmpty()) {
|
||||
toastInfo(getString(string.msg_err_select_tasks))
|
||||
requireActivity().flashInfo(getString(string.msg_err_select_tasks))
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,9 @@ import com.itsaky.androidide.utils.AndroidUtils;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.FileUtil;
|
||||
import com.itsaky.androidide.utils.FlashbarUtilsKt;
|
||||
import com.itsaky.androidide.utils.SingleTextWatcher;
|
||||
import com.itsaky.androidide.viewmodel.WizardViewModel;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
import com.itsaky.toaster.ToastUtilsKt;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@@ -133,7 +132,7 @@ public class WizardFragment extends BaseFragment {
|
||||
if (mProgressSheet != null && mProgressSheet.isShowing()) {
|
||||
mProgressSheet.dismiss();
|
||||
}
|
||||
ToastUtilsKt.toast(message, Toaster.Type.ERROR);
|
||||
FlashbarUtilsKt.flashError(message);
|
||||
});
|
||||
|
||||
mViewModel
|
||||
@@ -176,7 +175,7 @@ public class WizardFragment extends BaseFragment {
|
||||
if (mProgressSheet != null && mProgressSheet.isShowing()) {
|
||||
mProgressSheet.dismiss();
|
||||
}
|
||||
ToastUtilsKt.toast(string.project_created_successfully, Toaster.Type.SUCCESS);
|
||||
FlashbarUtilsKt.flashSuccess(string.project_created_successfully);
|
||||
|
||||
if (mListener != null) {
|
||||
getParentFragmentManager().popBackStack();
|
||||
@@ -223,7 +222,7 @@ public class WizardFragment extends BaseFragment {
|
||||
.substring("API".length() + 1, "API".length() + 3); // at least 2 digits
|
||||
return Integer.parseInt(sdk);
|
||||
} catch (Exception e) {
|
||||
ToastUtilsKt.toast(e.getMessage(), Toaster.Type.ERROR);
|
||||
FlashbarUtilsKt.flashError(e.getMessage());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -47,22 +47,24 @@ import com.itsaky.androidide.tasks.executeAsync
|
||||
import com.itsaky.androidide.utils.ApkInstaller
|
||||
import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.Environment
|
||||
import com.itsaky.androidide.utils.FlashType.ERROR
|
||||
import com.itsaky.androidide.utils.FlashType.SUCCESS
|
||||
import com.itsaky.androidide.utils.InstallationResultHandler
|
||||
import com.itsaky.androidide.utils.IntentUtils.startIntent
|
||||
import com.itsaky.androidide.utils.ProjectWriter
|
||||
import com.itsaky.androidide.utils.SingleTextWatcher
|
||||
import com.itsaky.toaster.Toaster.Type.ERROR
|
||||
import com.itsaky.toaster.Toaster.Type.SUCCESS
|
||||
import com.itsaky.toaster.toast
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import com.itsaky.androidide.utils.flashMessage
|
||||
import com.itsaky.androidide.utils.flashSuccess
|
||||
import com.unnamed.b.atv.model.TreeNode
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode.MAIN
|
||||
import java.io.File
|
||||
import java.util.Objects
|
||||
import java.util.regex.Pattern.compile
|
||||
import java.util.regex.Pattern.quote
|
||||
import javax.lang.model.SourceVersion
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode.MAIN
|
||||
|
||||
/**
|
||||
* Handles events related to files in filetree.
|
||||
@@ -141,7 +143,7 @@ class FileTreeActionHandler : BaseEventHandler() {
|
||||
when (option.id) {
|
||||
ID_COPY_PATH -> {
|
||||
ClipboardUtils.copyText("[AndroidIDE] Copied File Path", file.absolutePath)
|
||||
toast(string.copied, SUCCESS)
|
||||
flashSuccess(string.copied)
|
||||
}
|
||||
ID_RENAME_FILE -> renameFile(context, file)
|
||||
ID_DELETE_FILE -> delete(context, file)
|
||||
@@ -247,7 +249,7 @@ class FileTreeActionHandler : BaseEventHandler() {
|
||||
builder.setPositiveButton(string.text_create) { dialogInterface, _ ->
|
||||
dialogInterface.dismiss()
|
||||
if (binding.name.isErrorEnabled) {
|
||||
toast(string.msg_invalid_name, ERROR)
|
||||
flashError(string.msg_invalid_name)
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
@@ -255,7 +257,7 @@ class FileTreeActionHandler : BaseEventHandler() {
|
||||
val autoLayout = binding.checkButton.isChecked
|
||||
val pkgName = ProjectWriter.getPackageName(file)
|
||||
if (pkgName == null || pkgName.trim { it <= ' ' }.isEmpty()) {
|
||||
toast(string.msg_get_package_failed, ERROR)
|
||||
flashError(string.msg_get_package_failed)
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
@@ -312,12 +314,12 @@ class FileTreeActionHandler : BaseEventHandler() {
|
||||
val layoutName = ProjectWriter.createLayoutName(fileName.replace(".java", ".xml"))
|
||||
val newFileLayout = File(dir, layoutName)
|
||||
if (newFileLayout.exists()) {
|
||||
toast(string.msg_file_exists, ERROR)
|
||||
flashError(string.msg_file_exists)
|
||||
return
|
||||
}
|
||||
|
||||
if (!FileIOUtils.writeFileFromString(newFileLayout, ProjectWriter.createLayout())) {
|
||||
toast(string.msg_file_creation_failed, ERROR)
|
||||
flashError(string.msg_file_creation_failed)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -407,23 +409,23 @@ class FileTreeActionHandler : BaseEventHandler() {
|
||||
content: String
|
||||
): Boolean {
|
||||
if (name.length !in 1..40 || name.startsWith("/")) {
|
||||
toast(string.msg_invalid_name, ERROR)
|
||||
flashError(string.msg_invalid_name)
|
||||
return false
|
||||
}
|
||||
|
||||
val newFile = File(directory, name)
|
||||
if (newFile.exists()) {
|
||||
toast(string.msg_file_exists, ERROR)
|
||||
flashError(string.msg_file_exists)
|
||||
return false
|
||||
}
|
||||
if (!FileIOUtils.writeFileFromString(newFile, content)) {
|
||||
toast(string.msg_file_creation_failed, ERROR)
|
||||
flashError(string.msg_file_creation_failed)
|
||||
return false
|
||||
}
|
||||
|
||||
notifyFileCreated(newFile, context)
|
||||
// TODO Notify language servers about file created event
|
||||
toast(string.msg_file_created, SUCCESS)
|
||||
flashSuccess(string.msg_file_created)
|
||||
if (lastHeld != null) {
|
||||
val node = TreeNode(newFile)
|
||||
node.viewHolder = FileTreeViewHolder(context)
|
||||
@@ -448,22 +450,22 @@ class FileTreeActionHandler : BaseEventHandler() {
|
||||
dialogInterface.dismiss()
|
||||
val name: String = binding.name.editText!!.text.toString().trim()
|
||||
if (name.length !in 1..40 || name.startsWith("/")) {
|
||||
toast(string.msg_invalid_name, ERROR)
|
||||
flashError(string.msg_invalid_name)
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
val newDir = File(currentDir, name)
|
||||
if (newDir.exists()) {
|
||||
toast(string.msg_folder_exists, ERROR)
|
||||
flashError(string.msg_folder_exists)
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
if (!newDir.mkdirs()) {
|
||||
toast(string.msg_folder_creation_failed, ERROR)
|
||||
flashError(string.msg_folder_creation_failed)
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
toast(string.msg_folder_created, SUCCESS)
|
||||
flashSuccess(string.msg_folder_created)
|
||||
if (lastHeld != null) {
|
||||
val node = TreeNode(newDir)
|
||||
node.viewHolder = FileTreeViewHolder(context)
|
||||
@@ -491,7 +493,7 @@ class FileTreeActionHandler : BaseEventHandler() {
|
||||
|
||||
val deleted = it ?: false
|
||||
|
||||
toast(
|
||||
flashMessage(
|
||||
if (deleted) string.deleted else string.delete_failed,
|
||||
if (deleted) SUCCESS else ERROR
|
||||
)
|
||||
@@ -544,7 +546,10 @@ class FileTreeActionHandler : BaseEventHandler() {
|
||||
dialogInterface.dismiss()
|
||||
val name: String = binding.name.editText!!.text.toString().trim()
|
||||
val renamed = name.length in 1..40 && FileUtils.rename(file, name)
|
||||
toast(if (renamed) string.renamed else string.rename_failed, if (renamed) SUCCESS else ERROR)
|
||||
flashMessage(
|
||||
if (renamed) string.renamed else string.rename_failed,
|
||||
if (renamed) SUCCESS else ERROR
|
||||
)
|
||||
if (!renamed) {
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
@@ -46,10 +46,9 @@ import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.models.SearchResult;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.ui.editor.CodeEditorView;
|
||||
import com.itsaky.androidide.utils.FlashbarActivityUtilsKt;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.LSPUtils;
|
||||
import com.itsaky.toaster.ToastUtilsKt;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@@ -182,7 +181,7 @@ public class IDELanguageClientImpl implements ILanguageClient {
|
||||
"activity=" + activity(),
|
||||
"editor=" + editor,
|
||||
"action=" + action);
|
||||
ToastUtilsKt.toast(string.msg_cannot_perform_fix, Toaster.Type.ERROR);
|
||||
FlashbarActivityUtilsKt.flashError(activity(), string.msg_cannot_perform_fix);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -199,7 +198,7 @@ public class IDELanguageClientImpl implements ILanguageClient {
|
||||
if (result == null || throwable != null || !result) {
|
||||
LOG.error(
|
||||
"Unable to perform code action", "result=" + result, "throwable=" + throwable);
|
||||
ToastUtilsKt.toast(string.msg_cannot_perform_fix, Toaster.Type.ERROR);
|
||||
FlashbarActivityUtilsKt.flashError(activity(), string.msg_cannot_perform_fix);
|
||||
} else {
|
||||
editor.executeCommand(action.getCommand());
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@ import android.os.Build.VERSION_CODES
|
||||
import androidx.preference.Preference
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.itsaky.androidide.resources.R.drawable
|
||||
import com.itsaky.androidide.resources.R.string
|
||||
import com.itsaky.androidide.app.BaseApplication
|
||||
import com.itsaky.androidide.preferences.internal.CUSTOM_GRADLE_INSTALLATION
|
||||
import com.itsaky.androidide.preferences.internal.GRADLE_CLEAR_CACHE
|
||||
@@ -39,10 +37,12 @@ import com.itsaky.androidide.preferences.internal.isScanEnabled
|
||||
import com.itsaky.androidide.preferences.internal.isStacktraceEnabled
|
||||
import com.itsaky.androidide.preferences.internal.isWarningModeAllEnabled
|
||||
import com.itsaky.androidide.preferences.internal.tpFix
|
||||
import com.itsaky.androidide.resources.R.drawable
|
||||
import com.itsaky.androidide.resources.R.string
|
||||
import com.itsaky.androidide.tasks.executeAsync
|
||||
import com.itsaky.androidide.utils.Environment.GRADLE_USER_HOME
|
||||
import com.itsaky.toaster.toastError
|
||||
import com.itsaky.toaster.toastSuccess
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import com.itsaky.androidide.utils.flashSuccess
|
||||
import java.io.File
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@@ -162,7 +162,7 @@ private class TagPointersFix(
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
private class GradleClearCache (
|
||||
private class GradleClearCache(
|
||||
override val key: String = GRADLE_CLEAR_CACHE,
|
||||
override val title: Int = string.idepref_build_clearCache_title,
|
||||
override val summary: Int? = string.idepref_build_clearCache_summary,
|
||||
@@ -176,9 +176,9 @@ private class GradleClearCache (
|
||||
dlg.dismiss()
|
||||
executeAsync(callable = this::deleteCaches) {
|
||||
if (it == true) {
|
||||
toastSuccess(string.deleted)
|
||||
flashSuccess(string.deleted)
|
||||
} else {
|
||||
toastError(string.delete_failed)
|
||||
flashError(string.delete_failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
@@ -33,7 +32,6 @@ import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.transition.Slide
|
||||
import androidx.transition.TransitionManager
|
||||
import com.blankj.utilcode.util.KeyboardUtils
|
||||
import com.blankj.utilcode.util.SizeUtils
|
||||
@@ -59,8 +57,7 @@ import com.itsaky.androidide.ui.editor.CodeEditorView
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.IntentUtils.shareFile
|
||||
import com.itsaky.androidide.utils.Symbols.forFile
|
||||
import com.itsaky.toaster.Toaster.Type.ERROR
|
||||
import com.itsaky.toaster.toast
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.charset.StandardCharsets
|
||||
@@ -261,10 +258,16 @@ constructor(
|
||||
|
||||
val activity = context as Activity
|
||||
if (KeyboardUtils.isSoftInputVisible(activity)) {
|
||||
TransitionManager.beginDelayedTransition(binding.root, MaterialSharedAxis(MaterialSharedAxis.Y, false))
|
||||
TransitionManager.beginDelayedTransition(
|
||||
binding.root,
|
||||
MaterialSharedAxis(MaterialSharedAxis.Y, false)
|
||||
)
|
||||
binding.headerContainer.displayedChild = CHILD_SYMBOL_INPUT
|
||||
} else {
|
||||
TransitionManager.beginDelayedTransition(binding.root, MaterialSharedAxis(MaterialSharedAxis.Y, false))
|
||||
TransitionManager.beginDelayedTransition(
|
||||
binding.root,
|
||||
MaterialSharedAxis(MaterialSharedAxis.Y, false)
|
||||
)
|
||||
binding.headerContainer.displayedChild = CHILD_HEADER
|
||||
}
|
||||
}
|
||||
@@ -291,7 +294,7 @@ constructor(
|
||||
@Suppress("DEPRECATION")
|
||||
private fun shareText(text: String?, type: String) {
|
||||
if (text == null || TextUtils.isEmpty(text)) {
|
||||
toast(context.getString(string.msg_output_text_extraction_failed), ERROR)
|
||||
flashError(context.getString(string.msg_output_text_extraction_failed))
|
||||
return
|
||||
}
|
||||
val pd = ProgressDialog.show(context, null, context.getString(string.please_wait), true, false)
|
||||
|
||||
@@ -8,17 +8,16 @@ import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.blankj.utilcode.util.CollectionUtils;
|
||||
import com.itsaky.androidide.resources.R;
|
||||
import com.itsaky.androidide.interfaces.ProjectWriterCallback;
|
||||
import com.itsaky.androidide.models.NewProjectDetails;
|
||||
import com.itsaky.androidide.models.ProjectTemplate;
|
||||
import com.itsaky.androidide.resources.R;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.tasks.callables.ProjectCreatorCallable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class WizardViewModel extends AndroidViewModel {
|
||||
|
||||
|
||||
+3
-1
@@ -26,7 +26,9 @@ plugins {
|
||||
alias(libs.plugins.kotlin) apply false
|
||||
}
|
||||
|
||||
buildscript { dependencies { classpath("com.google.android.gms:oss-licenses-plugin:0.10.6") } }
|
||||
buildscript { dependencies { classpath("com.google.android.gms:oss-licenses-plugin:0.10.6")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0")
|
||||
} }
|
||||
|
||||
val Project.projectVersionCode by lazy {
|
||||
val version = rootProject.version.toString()
|
||||
|
||||
@@ -25,6 +25,7 @@ dependencies {
|
||||
implementation(projects.eventbusEvents)
|
||||
implementation(projects.lexers)
|
||||
implementation(projects.resources)
|
||||
implementation(projects.subprojects.flashbar)
|
||||
|
||||
api(projects.shared)
|
||||
api(projects.logger)
|
||||
|
||||
@@ -34,9 +34,8 @@ import com.itsaky.androidide.resources.R;
|
||||
import com.itsaky.androidide.shell.ShellServer;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.FileUtil;
|
||||
import com.itsaky.androidide.utils.FlashbarUtilsKt;
|
||||
import com.itsaky.androidide.utils.JavaCharacter;
|
||||
import com.itsaky.toaster.ToastUtilsKt;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
@@ -88,7 +87,6 @@ public abstract class BaseApplication extends Application {
|
||||
public void onCreate() {
|
||||
instance = this;
|
||||
Environment.init();
|
||||
ToastUtilsKt.init();
|
||||
super.onCreate();
|
||||
|
||||
mPrefsManager = new PreferenceManager(this);
|
||||
@@ -200,7 +198,7 @@ public abstract class BaseApplication extends Application {
|
||||
if (pkg != null) {
|
||||
openUrl(url);
|
||||
} else {
|
||||
ToastUtilsKt.toast(th.getMessage(), Toaster.Type.ERROR);
|
||||
FlashbarUtilsKt.flashError(th.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,16 +40,16 @@ import androidx.core.provider.DocumentsContractCompat.getTreeDocumentId
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.itsaky.androidide.resources.R.string
|
||||
import com.itsaky.toaster.Toaster.Type.ERROR
|
||||
import com.itsaky.toaster.toast
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import java.io.File
|
||||
|
||||
open class BaseFragment @JvmOverloads constructor(contentLayoutId: Int = 0) : Fragment(contentLayoutId) {
|
||||
open class BaseFragment @JvmOverloads constructor(contentLayoutId: Int = 0) :
|
||||
Fragment(contentLayoutId) {
|
||||
|
||||
private var callback: OnDirectoryPickedCallback? = null
|
||||
private val allowedAuthorities =
|
||||
setOf(ANDROID_DOCS_AUTHORITY, ANDROIDIDE_DOCS_AUTHORITY, TERMUX_DOCS_AUTHORITY)
|
||||
|
||||
|
||||
companion object {
|
||||
const val ANDROID_DOCS_AUTHORITY = "com.android.externalstorage.documents"
|
||||
const val ANDROIDIDE_DOCS_AUTHORITY = "com.itsaky.androidide.documents"
|
||||
@@ -63,12 +63,12 @@ open class BaseFragment @JvmOverloads constructor(contentLayoutId: Int = 0) : Fr
|
||||
val pickedDir = DocumentFile.fromTreeUri(context, uri)
|
||||
|
||||
if (pickedDir == null) {
|
||||
toast(getString(string.err_invalid_data_by_intent), ERROR)
|
||||
flashError(string.err_invalid_data_by_intent)
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
if (!pickedDir.exists()) {
|
||||
toast(getString(string.msg_picked_isnt_dir), ERROR)
|
||||
flashError(getString(string.msg_picked_isnt_dir))
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ open class BaseFragment @JvmOverloads constructor(contentLayoutId: Int = 0) : Fr
|
||||
val authority = docUri.authority
|
||||
|
||||
if (!allowedAuthorities.contains(authority)) {
|
||||
toast(getString(string.err_authority_not_allowed, authority), ERROR)
|
||||
flashError(getString(string.err_authority_not_allowed, authority))
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ open class BaseFragment @JvmOverloads constructor(contentLayoutId: Int = 0) : Fr
|
||||
} else {
|
||||
val split = docId.split(':')
|
||||
if ("primary" != split[0]) {
|
||||
toast(getString(string.msg_select_from_primary_storage), ERROR)
|
||||
flashError(getString(string.msg_select_from_primary_storage))
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ open class BaseFragment @JvmOverloads constructor(contentLayoutId: Int = 0) : Fr
|
||||
}
|
||||
|
||||
if (!dir.exists() || !dir.isDirectory) {
|
||||
toast(getString(string.err_invalid_data_by_intent), ERROR)
|
||||
flashError(getString(string.err_invalid_data_by_intent))
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuff.Mode.SRC_ATOP
|
||||
import android.widget.ImageView.ScaleType
|
||||
import android.widget.ImageView.ScaleType.FIT_CENTER
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.annotation.StringRes
|
||||
import com.itsaky.androidide.flashbar.Flashbar
|
||||
import com.itsaky.androidide.flashbar.Flashbar.Gravity.TOP
|
||||
import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.utils.FlashType.ERROR
|
||||
import com.itsaky.androidide.utils.FlashType.INFO
|
||||
import com.itsaky.androidide.utils.FlashType.SUCCESS
|
||||
|
||||
const val LENGTH_SHORT = 2000L
|
||||
const val LENGTH_LONG = 3500L
|
||||
|
||||
private val COLOR_SUCCESS = Color.parseColor("#4CAF50")
|
||||
private val COLOR_ERROR = Color.parseColor("#f44336")
|
||||
private const val COLOR_INFO = Color.DKGRAY
|
||||
|
||||
fun Activity.flashbarBuilder(duration: Long = LENGTH_SHORT): Flashbar.Builder {
|
||||
return Flashbar.Builder(this)
|
||||
.gravity(TOP)
|
||||
.duration(duration)
|
||||
.backgroundColor(resolveAttr(R.attr.colorPrimaryContainer))
|
||||
.messageColor(resolveAttr(R.attr.colorOnPrimaryContainer))
|
||||
}
|
||||
|
||||
fun Activity.flashMessage(msg: String?, type: FlashType) {
|
||||
msg ?: return
|
||||
when (type) {
|
||||
ERROR -> flashError(msg)
|
||||
INFO -> flashInfo(msg)
|
||||
SUCCESS -> flashSuccess(msg)
|
||||
}
|
||||
}
|
||||
|
||||
fun Activity.flashMessage(@StringRes msg: Int, type: FlashType) {
|
||||
when (type) {
|
||||
ERROR -> flashError(msg)
|
||||
INFO -> flashInfo(msg)
|
||||
SUCCESS -> flashSuccess(msg)
|
||||
}
|
||||
}
|
||||
|
||||
fun Activity.flashSuccess(msg: String?) {
|
||||
msg ?: return
|
||||
flashbarBuilder().successIcon().message(msg).show()
|
||||
}
|
||||
|
||||
fun Activity.flashError(msg: String?) {
|
||||
msg ?: return
|
||||
flashbarBuilder().errorIcon().message(msg).show()
|
||||
}
|
||||
|
||||
fun Activity.flashInfo(msg: String?) {
|
||||
msg ?: return
|
||||
flashbarBuilder().infoIcon().message(msg).show()
|
||||
}
|
||||
|
||||
fun Activity.flashSuccess(@StringRes msg: Int) {
|
||||
flashbarBuilder().successIcon().message(msg).show()
|
||||
}
|
||||
|
||||
fun Activity.flashError(@StringRes msg: Int) {
|
||||
flashbarBuilder().errorIcon().message(msg).show()
|
||||
}
|
||||
|
||||
fun Activity.flashInfo(@StringRes msg: Int) {
|
||||
flashbarBuilder().infoIcon().message(msg).show()
|
||||
}
|
||||
|
||||
private fun Flashbar.Builder.successIcon(): Flashbar.Builder {
|
||||
return withIcon(R.drawable.ic_ok, colorFilter = COLOR_SUCCESS)
|
||||
}
|
||||
|
||||
private fun Flashbar.Builder.errorIcon(): Flashbar.Builder {
|
||||
return withIcon(R.drawable.ic_error, colorFilter = COLOR_ERROR)
|
||||
}
|
||||
|
||||
private fun Flashbar.Builder.infoIcon(): Flashbar.Builder {
|
||||
return withIcon(R.drawable.ic_info, colorFilter = COLOR_INFO)
|
||||
}
|
||||
|
||||
private fun Flashbar.Builder.withIcon(
|
||||
@DrawableRes icon: Int,
|
||||
@FloatRange(from = 0.0, to = 1.0) scale: Float = 1.0f,
|
||||
@ColorInt colorFilter: Int = -1,
|
||||
colorFilterMode: PorterDuff.Mode = SRC_ATOP,
|
||||
scaleType: ScaleType = FIT_CENTER
|
||||
): Flashbar.Builder {
|
||||
return showIcon(scale = scale, scaleType = scaleType).icon(icon).also {
|
||||
if (colorFilter != -1) {
|
||||
iconColorFilter(colorFilter, colorFilterMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.utils
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.annotation.StringRes
|
||||
import com.blankj.utilcode.util.ActivityUtils
|
||||
import com.itsaky.androidide.flashbar.Flashbar
|
||||
|
||||
fun flashbarBuilder(): Flashbar.Builder? {
|
||||
return withActivity { flashbarBuilder() }
|
||||
}
|
||||
|
||||
fun flashMessage(msg: String?, type: FlashType) {
|
||||
withActivity { flashMessage(msg, type) }
|
||||
}
|
||||
|
||||
fun flashMessage(@StringRes msg: Int, type: FlashType) {
|
||||
withActivity { flashMessage(msg, type) }
|
||||
}
|
||||
|
||||
fun flashSuccess(msg: String?) {
|
||||
withActivity { flashSuccess(msg) }
|
||||
}
|
||||
|
||||
fun flashSuccess(@StringRes msg: Int) {
|
||||
withActivity { flashSuccess(msg) }
|
||||
}
|
||||
|
||||
fun flashError(msg: String?) {
|
||||
withActivity { flashError(msg) }
|
||||
}
|
||||
|
||||
fun flashError(@StringRes msg: Int) {
|
||||
withActivity { flashError(msg) }
|
||||
}
|
||||
|
||||
fun flashInfo(msg: String?) {
|
||||
withActivity { flashInfo(msg) }
|
||||
}
|
||||
|
||||
fun flashInfo(@StringRes msg: Int) {
|
||||
withActivity { flashInfo(msg) }
|
||||
}
|
||||
|
||||
private fun <T> withActivity(action: Activity.() -> T?): T? {
|
||||
return ActivityUtils.getTopActivity()?.let { it.action() }
|
||||
?: run {
|
||||
ILogger.instance().warn("Cannot show flashbar message. Cannot get top activity.")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/** The type of flashbar message. */
|
||||
enum class FlashType {
|
||||
ERROR,
|
||||
INFO,
|
||||
SUCCESS
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* 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.toaster
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import android.view.ViewAnimationUtils.createCircularReveal
|
||||
import android.widget.LinearLayout
|
||||
import com.google.android.material.animation.ArgbEvaluatorCompat
|
||||
import kotlin.math.max
|
||||
|
||||
class ToastWrapper : LinearLayout {
|
||||
constructor(context: Context?) : super(context)
|
||||
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context?, attrs: AttributeSet?, style: Int) : super(context, attrs, style)
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
startRevealAnimation()
|
||||
startIconColorChangeAnimation()
|
||||
}
|
||||
|
||||
private fun startIconColorChangeAnimation() {
|
||||
val anim = ValueAnimator.ofObject(ArgbEvaluatorCompat(), Color.WHITE, toaster.iconColor)
|
||||
anim.duration = (Toaster.REVEAL_ANIM_DURATION + 300).toLong()
|
||||
anim.addUpdateListener { animator: ValueAnimator ->
|
||||
toaster.icon!!.setTint(animator.animatedValue as Int)
|
||||
}
|
||||
anim.start()
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
setBackgroundColor(Color.TRANSPARENT)
|
||||
}
|
||||
|
||||
private fun startRevealAnimation() {
|
||||
this.background = toaster.backgroundDrawable
|
||||
val x = left
|
||||
val y = (top + bottom) / 2
|
||||
val endRadius = max(height, width)
|
||||
createCircularReveal(this, x, y, 0f, endRadius.toFloat()).also {
|
||||
it.duration = Toaster.REVEAL_ANIM_DURATION.toLong()
|
||||
it.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
/*
|
||||
* 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.toaster
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff.Mode.SRC_ATOP
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat.create
|
||||
import com.blankj.utilcode.util.SizeUtils
|
||||
import com.blankj.utilcode.util.ThreadUtils
|
||||
import com.itsaky.androidide.app.BaseApplication.getBaseInstance
|
||||
import com.itsaky.androidide.resources.R.color
|
||||
import com.itsaky.androidide.resources.R.drawable
|
||||
import com.itsaky.androidide.common.databinding.LayoutToastBinding
|
||||
import com.itsaky.toaster.Toaster.Gravity.BOTTOM_LEFT
|
||||
import com.itsaky.toaster.Toaster.Gravity.BOTTOM_RIGHT
|
||||
import com.itsaky.toaster.Toaster.Gravity.TOP_LEFT
|
||||
import com.itsaky.toaster.Toaster.Gravity.TOP_RIGHT
|
||||
import com.itsaky.toaster.Toaster.Type.ERROR
|
||||
import com.itsaky.toaster.Toaster.Type.INFO
|
||||
import com.itsaky.toaster.Toaster.Type.SUCCESS
|
||||
|
||||
class Toaster internal constructor(context: Context) {
|
||||
|
||||
enum class Type {
|
||||
ERROR,
|
||||
SUCCESS,
|
||||
INFO
|
||||
}
|
||||
|
||||
enum class Gravity {
|
||||
TOP_LEFT,
|
||||
TOP_RIGHT,
|
||||
BOTTOM_LEFT,
|
||||
BOTTOM_RIGHT
|
||||
}
|
||||
|
||||
companion object {
|
||||
val COLOR_SUCCESS = Color.parseColor("#4CAF50")
|
||||
val COLOR_ERROR = Color.parseColor("#F44336")
|
||||
const val REVEAL_ANIM_DURATION = 500
|
||||
const val SHORT = 2000
|
||||
const val LONG = 3500
|
||||
}
|
||||
|
||||
internal val backgroundDrawable: Drawable = createBackgroundDrawable(context)
|
||||
private var binding: LayoutToastBinding? = null
|
||||
|
||||
var gravity: Gravity? = null
|
||||
private set
|
||||
var type: Type = INFO
|
||||
private set
|
||||
var icon: AnimatedVectorDrawableCompat? = null
|
||||
private set
|
||||
var iconColor = Color.DKGRAY
|
||||
private set
|
||||
var duration = 0
|
||||
private set
|
||||
|
||||
init {
|
||||
createView(context)
|
||||
}
|
||||
|
||||
private fun createBackgroundDrawable(context: Context): Drawable {
|
||||
val drawable = GradientDrawable()
|
||||
drawable.shape = GradientDrawable.RECTANGLE
|
||||
drawable.setColor(ContextCompat.getColor(context, color.color_toast_background))
|
||||
drawable.cornerRadius = 25f
|
||||
return drawable
|
||||
}
|
||||
|
||||
private fun createView(context: Context) {
|
||||
binding = LayoutToastBinding.inflate(LayoutInflater.from(context))
|
||||
}
|
||||
|
||||
fun show() {
|
||||
ThreadUtils.runOnUiThread(this::showInternal)
|
||||
}
|
||||
|
||||
fun setText(text: String?): Toaster {
|
||||
binding!!.toastText.text = text
|
||||
return this
|
||||
}
|
||||
|
||||
fun setText(textResId: Int): Toaster {
|
||||
binding!!.toastText.setText(textResId)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setDuration(duration: Int): Toaster {
|
||||
this.duration = duration + REVEAL_ANIM_DURATION
|
||||
return this
|
||||
}
|
||||
|
||||
fun setType(type: Type): Toaster {
|
||||
this.type = type
|
||||
return this
|
||||
}
|
||||
|
||||
fun setGravity(gravity: Gravity?): Toaster {
|
||||
this.gravity = gravity
|
||||
return this
|
||||
}
|
||||
|
||||
private fun showInternal() {
|
||||
val dp16 = SizeUtils.dp2px(16f)
|
||||
iconColor = getIconColor()
|
||||
icon = createToastIconAnimation(iconColor)
|
||||
val gravity = getGravity()
|
||||
val mToast = Toast.makeText(getBaseInstance(), binding!!.toastText.text, duration)
|
||||
mToast.setGravity(gravity, dp16, dp16)
|
||||
mToast.duration = duration
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
mToast.view = binding!!.root
|
||||
|
||||
mToast.show()
|
||||
binding!!.toastImage.setImageDrawable(icon)
|
||||
icon!!.start()
|
||||
}
|
||||
|
||||
@JvmName("getIconColorInternal")
|
||||
private fun getIconColor(): Int {
|
||||
return when (type) {
|
||||
SUCCESS -> COLOR_SUCCESS
|
||||
ERROR -> COLOR_ERROR
|
||||
else -> Color.DKGRAY
|
||||
}
|
||||
}
|
||||
|
||||
private fun getGravity(): Int {
|
||||
return when (gravity) {
|
||||
TOP_LEFT -> android.view.Gravity.TOP or android.view.Gravity.START
|
||||
TOP_RIGHT -> android.view.Gravity.TOP or android.view.Gravity.END
|
||||
BOTTOM_RIGHT -> android.view.Gravity.BOTTOM or android.view.Gravity.END
|
||||
BOTTOM_LEFT -> android.view.Gravity.BOTTOM or android.view.Gravity.START
|
||||
else -> android.view.Gravity.TOP or android.view.Gravity.END
|
||||
}
|
||||
}
|
||||
|
||||
private fun createToastIconAnimation(tintColor: Int): AnimatedVectorDrawableCompat? {
|
||||
val imageDrawable =
|
||||
create(getBaseInstance(), getIconId())?.also {
|
||||
it.setTint(tintColor)
|
||||
it.setTintMode(SRC_ATOP)
|
||||
}
|
||||
binding!!.toastImage.setImageDrawable(imageDrawable)
|
||||
return imageDrawable
|
||||
}
|
||||
|
||||
private fun getIconId(): Int {
|
||||
return if (type == SUCCESS) {
|
||||
drawable.ic_ok
|
||||
} else {
|
||||
drawable.ic_error
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* 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.toaster
|
||||
|
||||
import com.itsaky.androidide.app.BaseApplication.getBaseInstance
|
||||
import com.itsaky.toaster.Toaster.Type
|
||||
import com.itsaky.toaster.Toaster.Type.ERROR
|
||||
import com.itsaky.toaster.Toaster.Type.INFO
|
||||
import com.itsaky.toaster.Toaster.Type.SUCCESS
|
||||
|
||||
internal lateinit var toaster: Toaster
|
||||
|
||||
internal fun init() {
|
||||
toaster = Toaster(getBaseInstance())
|
||||
}
|
||||
|
||||
fun toast(msg: String?, type: Type) {
|
||||
toaster.setDuration(Toaster.SHORT).setText(msg).setType(type).show()
|
||||
}
|
||||
|
||||
fun toast(msgResId: Int, type: Type) {
|
||||
toaster.setDuration(Toaster.SHORT).setText(msgResId).setType(type).show()
|
||||
}
|
||||
|
||||
fun toastSuccess(msg: String?) {
|
||||
toast(msg, SUCCESS)
|
||||
}
|
||||
|
||||
fun toastError(msg: String?) {
|
||||
toast(msg, ERROR)
|
||||
}
|
||||
|
||||
fun toastInfo(msg: String?) {
|
||||
toast(msg, INFO)
|
||||
}
|
||||
|
||||
fun toastSuccess(msgResId: Int) {
|
||||
toast(msgResId, SUCCESS)
|
||||
}
|
||||
|
||||
fun toastError(msgResId: Int) {
|
||||
toast(msgResId, ERROR)
|
||||
}
|
||||
|
||||
fun toastInfo(msgResId: Int) {
|
||||
toast(msgResId, INFO)
|
||||
}
|
||||
|
||||
fun toastLong(msg: String?, type: Type) {
|
||||
toaster.setDuration(Toaster.LONG).setText(msg).setType(type).show()
|
||||
}
|
||||
|
||||
fun toastLong(msgResId: Int, type: Type) {
|
||||
toaster.setDuration(Toaster.LONG).setText(msgResId).setType(type).show()
|
||||
}
|
||||
|
||||
fun toastLongSuccess(msg: String?) {
|
||||
toastLong(msg, SUCCESS)
|
||||
}
|
||||
|
||||
fun toastLongError(msg: String?) {
|
||||
toastLong(msg, ERROR)
|
||||
}
|
||||
|
||||
fun toastLongInfo(msg: String?) {
|
||||
toastLong(msg, INFO)
|
||||
}
|
||||
|
||||
fun toastLongSuccess(msgResId: Int) {
|
||||
toastLong(msgResId, SUCCESS)
|
||||
}
|
||||
|
||||
fun toastLongError(msgResId: Int) {
|
||||
toastLong(msgResId, ERROR)
|
||||
}
|
||||
|
||||
fun toastLongInfo(msgResId: Int) {
|
||||
toastLong(msgResId, INFO)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.itsaky.toaster.ToastWrapper
|
||||
android:id="@+id/toast_toastWrapper"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/toast_image"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@android:drawable/ic_delete"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/toast_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
</com.itsaky.toaster.ToastWrapper>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -19,8 +19,6 @@ package com.itsaky.androidide.editor.ui;
|
||||
import static com.itsaky.androidide.preferences.internal.EditorPreferencesKt.getTabSize;
|
||||
import static com.itsaky.androidide.preferences.internal.EditorPreferencesKt.getVisiblePasswordFlag;
|
||||
import static com.itsaky.androidide.resources.R.string;
|
||||
import static com.itsaky.toaster.ToastUtilsKt.toast;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
@@ -59,8 +57,8 @@ import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
|
||||
import com.itsaky.androidide.utils.DocumentUtils;
|
||||
import com.itsaky.androidide.utils.FlashbarUtilsKt;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
@@ -70,7 +68,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
import io.github.rosemoe.sora.event.ContentChangeEvent;
|
||||
import io.github.rosemoe.sora.event.SelectionChangeEvent;
|
||||
import io.github.rosemoe.sora.event.Unsubscribe;
|
||||
import io.github.rosemoe.sora.text.Content;
|
||||
import io.github.rosemoe.sora.widget.CodeEditor;
|
||||
import io.github.rosemoe.sora.widget.IDEEditorSearcher;
|
||||
import io.github.rosemoe.sora.widget.component.EditorAutoCompletion;
|
||||
@@ -475,7 +472,7 @@ public class IDEEditor extends CodeEditor implements com.itsaky.androidide.edito
|
||||
}
|
||||
|
||||
final var locations = result.getLocations();
|
||||
if (locations.size() <= 0) {
|
||||
if (locations.size() == 0) {
|
||||
LOG.error("No definitions found", "Size:", locations.size());
|
||||
showDefinitionNotFound(pd);
|
||||
return;
|
||||
@@ -515,7 +512,7 @@ public class IDEEditor extends CodeEditor implements com.itsaky.androidide.edito
|
||||
//noinspection ConstantConditions
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
toast(string.msg_no_definition, Toaster.Type.ERROR);
|
||||
FlashbarUtilsKt.flashError(string.msg_no_definition);
|
||||
pd.dismiss();
|
||||
});
|
||||
}
|
||||
@@ -613,7 +610,7 @@ public class IDEEditor extends CodeEditor implements com.itsaky.androidide.edito
|
||||
//noinspection ConstantConditions
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
toast(string.msg_no_references, Toaster.Type.ERROR);
|
||||
FlashbarUtilsKt.flashError(string.msg_no_references);
|
||||
pd.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,13 +24,12 @@ import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.newDialogBuilder
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindTypeDeclarationAt
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.toaster.Toaster
|
||||
import com.itsaky.toaster.toast
|
||||
import com.itsaky.androidide.utils.flashInfo
|
||||
import com.sun.source.tree.ClassTree
|
||||
import com.sun.source.tree.Tree.Kind.VARIABLE
|
||||
import com.sun.source.tree.VariableTree
|
||||
@@ -134,7 +133,7 @@ abstract class FieldBasedAction : BaseJavaCodeAction() {
|
||||
}
|
||||
|
||||
if (result.isEmpty()) {
|
||||
toast(data[Context::class.java]!!.getString(R.string.msg_no_fields_found), Toaster.Type.INFO)
|
||||
flashInfo(data[Context::class.java]!!.getString(R.string.msg_no_fields_found))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -175,10 +174,7 @@ abstract class FieldBasedAction : BaseJavaCodeAction() {
|
||||
dialog.dismiss()
|
||||
|
||||
if (checkedNames.isEmpty()) {
|
||||
toast(
|
||||
data[Context::class.java]!!.getString(R.string.msg_no_fields_selected),
|
||||
Toaster.Type.ERROR
|
||||
)
|
||||
flashInfo(data[Context::class.java]!!.getString(R.string.msg_no_fields_selected))
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
|
||||
+3
-5
@@ -33,9 +33,7 @@ import com.itsaky.androidide.lsp.java.utils.ShortTypePrinter.NO_PACKAGE
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.resources.R.string
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.toaster.Toaster.Type.ERROR
|
||||
import com.itsaky.toaster.toast
|
||||
import com.itsaky.toaster.toastLong
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import com.sun.source.tree.ClassTree
|
||||
import com.sun.source.tree.VariableTree
|
||||
import com.sun.source.util.TreePath
|
||||
@@ -65,7 +63,7 @@ class GenerateConstructorAction : FieldBasedAction() {
|
||||
.whenComplete { _, error ->
|
||||
if (error != null) {
|
||||
log.error("Unable to generate constructor for the selected fields", error)
|
||||
toast(string.msg_cannot_generate_constructor, ERROR)
|
||||
flashError(string.msg_cannot_generate_constructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,7 +105,7 @@ class GenerateConstructorAction : FieldBasedAction() {
|
||||
log.warn(
|
||||
"A constructor with same parameter types is already available in class ${type.simpleName}"
|
||||
)
|
||||
toastLong(data[Context::class.java]!!.getString(string.msg_constructor_available), ERROR)
|
||||
flashError(data[Context::class.java]!!.getString(string.msg_constructor_available))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+3
-5
@@ -37,8 +37,7 @@ import com.itsaky.androidide.lsp.java.utils.TypeUtils.toType
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.toaster.Toaster
|
||||
import com.itsaky.toaster.toast
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import com.sun.source.tree.ClassTree
|
||||
import com.sun.source.util.TreePath
|
||||
import com.sun.source.util.Trees
|
||||
@@ -69,9 +68,8 @@ class GenerateSettersAndGettersAction : FieldBasedAction() {
|
||||
if (error != null) {
|
||||
log.error("Unable to generate setters and getters", error)
|
||||
ThreadUtils.runOnUiThread {
|
||||
toast(
|
||||
data[Context::class.java]!!.getString(R.string.msg_cannot_generate_setters_getters),
|
||||
Toaster.Type.ERROR
|
||||
flashError(
|
||||
data[Context::class.java]!!.getString(R.string.msg_cannot_generate_setters_getters)
|
||||
)
|
||||
}
|
||||
return@whenComplete
|
||||
|
||||
+6
-8
@@ -26,15 +26,14 @@ import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.resources.R.string
|
||||
import com.itsaky.androidide.lsp.java.actions.FieldBasedAction
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.resources.R.string
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.toaster.Toaster.Type.ERROR
|
||||
import com.itsaky.toaster.toast
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import com.sun.source.tree.ClassTree
|
||||
import com.sun.source.tree.VariableTree
|
||||
import com.sun.source.util.TreePath
|
||||
@@ -67,9 +66,8 @@ class GenerateToStringMethodAction : FieldBasedAction() {
|
||||
if (error != null) {
|
||||
log.error("Unable to generate toString() implementation", error)
|
||||
ThreadUtils.runOnUiThread {
|
||||
toast(
|
||||
data[Context::class.java]!!.getString(R.string.msg_cannot_generate_toString),
|
||||
ERROR
|
||||
flashError(
|
||||
data[Context::class.java]!!.getString(R.string.msg_cannot_generate_toString)
|
||||
)
|
||||
}
|
||||
return@whenComplete
|
||||
@@ -106,7 +104,7 @@ class GenerateToStringMethodAction : FieldBasedAction() {
|
||||
) {
|
||||
if (isToStringOverridden(task, type)) {
|
||||
ThreadUtils.runOnUiThread {
|
||||
toast(data[Context::class.java]!!.getString(string.msg_toString_overridden), ERROR)
|
||||
flashError(data[Context::class.java]!!.getString(string.msg_toString_overridden))
|
||||
}
|
||||
log.warn("toString() method has already been overridden in class ${type.simpleName}")
|
||||
return
|
||||
|
||||
+7
-14
@@ -25,7 +25,6 @@ import com.itsaky.androidide.actions.newDialogBuilder
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompilerProvider
|
||||
@@ -37,9 +36,9 @@ import com.itsaky.androidide.lsp.java.utils.JavaParserUtils
|
||||
import com.itsaky.androidide.lsp.java.utils.MethodPtr
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindTypeDeclarationAt
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.toaster.Toaster
|
||||
import com.itsaky.toaster.toast
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import com.sun.source.tree.MethodTree
|
||||
import com.sun.source.util.Trees
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
@@ -147,10 +146,7 @@ class OverrideSuperclassMethodsAction : BaseJavaCodeAction() {
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is List<*> || result.isEmpty() || position < 0) {
|
||||
log.warn("Unable to find any overridable method")
|
||||
toast(
|
||||
data[Context::class.java]!!.getString(R.string.msg_no_overridable_methods),
|
||||
Toaster.Type.ERROR
|
||||
)
|
||||
flashError(data[Context::class.java]!!.getString(R.string.msg_no_overridable_methods))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -172,10 +168,7 @@ class OverrideSuperclassMethodsAction : BaseJavaCodeAction() {
|
||||
dialog.dismiss()
|
||||
|
||||
if (checkedMethods.isEmpty()) {
|
||||
toast(
|
||||
data[Context::class.java]!!.getString(R.string.msg_no_methods_selected),
|
||||
Toaster.Type.ERROR
|
||||
)
|
||||
flashError(data[Context::class.java]!!.getString(R.string.msg_no_methods_selected))
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
@@ -186,9 +179,8 @@ class OverrideSuperclassMethodsAction : BaseJavaCodeAction() {
|
||||
log.error("An error occurred overriding methods")
|
||||
|
||||
ThreadUtils.runOnUiThread {
|
||||
toast(
|
||||
data[Context::class.java]!!.getString(R.string.msg_cannot_override_methods),
|
||||
Toaster.Type.ERROR
|
||||
flashError(
|
||||
data[Context::class.java]!!.getString(R.string.msg_cannot_override_methods)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -198,6 +190,7 @@ class OverrideSuperclassMethodsAction : BaseJavaCodeAction() {
|
||||
builder.show()
|
||||
}
|
||||
|
||||
@Suppress("Since15")
|
||||
private fun overrideMethods(data: ActionData, checkedMethods: MutableList<MethodPtr>) {
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return)
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
import com.mooltiverse.oss.nyx.gradle.NyxExtension
|
||||
import com.mooltiverse.oss.nyx.services.github.GitHub
|
||||
|
||||
include(":subprojects:flashbar")
|
||||
|
||||
|
||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||
|
||||
pluginManagement {
|
||||
|
||||
@@ -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,33 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.itsaky.androidide.flashbar"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.annotation)
|
||||
implementation(libs.androidx.ktx)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.google.material)
|
||||
}
|
||||
+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,674 @@
|
||||
/*
|
||||
* 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
|
||||
private 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)
|
||||
|
||||
flashbarView = FlashbarView(builder.activity)
|
||||
flashbarView.init(builder.gravity, builder.castShadow, builder.shadowStrength)
|
||||
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 castShadow: Boolean = true
|
||||
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 > 0) { "Duration can not be negative or zero" }
|
||||
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 if the flashbar should cast shadow and the strength of it */
|
||||
@JvmOverloads
|
||||
fun castShadow(shadow: Boolean = true, strength: Int = DEFAULT_SHADOW_STRENGTH) = apply {
|
||||
require(strength > 0) { "Shadow strength can not be negative or zero" }
|
||||
|
||||
this.castShadow = shadow
|
||||
this.shadowStrength = strength
|
||||
}
|
||||
|
||||
/** 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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* 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 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)
|
||||
}
|
||||
})
|
||||
|
||||
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 (isBarDismissing || isBarShowing || !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,484 @@
|
||||
/*
|
||||
* 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.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 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.convertDpToPx
|
||||
import com.itsaky.androidide.flashbar.util.getStatusBarHeightInPx
|
||||
import com.itsaky.androidide.flashbar.view.FbButton
|
||||
import com.itsaky.androidide.flashbar.view.FbProgress
|
||||
import com.itsaky.androidide.flashbar.view.ShadowView
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
internal 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: FbProgress
|
||||
get() = this.binding.fbLeftProgress
|
||||
|
||||
private val fbRightProgress: FbProgress
|
||||
get() = this.binding.fbRightProgress
|
||||
|
||||
private val fbMessage: TextView
|
||||
get() = this.binding.fbMessage
|
||||
|
||||
private val fbPrimaryAction: FbButton
|
||||
get() = this.binding.fbPrimaryAction
|
||||
|
||||
private val fbPositiveAction: FbButton
|
||||
get() = this.binding.fbPositiveAction
|
||||
|
||||
private val fbNegativeAction: FbButton
|
||||
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, castShadow: Boolean, shadowStrength: Int) {
|
||||
this.gravity = gravity
|
||||
this.orientation = VERTICAL
|
||||
|
||||
// If the bar appears with the bottom, then the shadow needs to added to the top of it,
|
||||
// Thus, before the inflation of the bar
|
||||
if (castShadow && gravity == BOTTOM) {
|
||||
castShadow(ShadowView.ShadowType.TOP, shadowStrength)
|
||||
}
|
||||
|
||||
this.binding = FlashBarViewBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
|
||||
// If the bar appears with the top, then the shadow needs to added to the bottom of it,
|
||||
// Thus, after the inflation of the bar
|
||||
if (castShadow && gravity == TOP) {
|
||||
castShadow(ShadowView.ShadowType.BOTTOM, shadowStrength)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
internal fun setBarBackgroundDrawable(drawable: Drawable?) {
|
||||
if (drawable == null) return
|
||||
this.fbRoot.background = drawable
|
||||
}
|
||||
|
||||
internal fun setBarBackgroundColor(@ColorInt color: Int?) {
|
||||
if (color == null) return
|
||||
this.fbRoot.setBackgroundColor(color)
|
||||
}
|
||||
|
||||
internal fun setBarTapListener(listener: OnTapListener?) {
|
||||
if (listener == null) return
|
||||
|
||||
this.fbRoot.setOnClickListener { listener.onTap(parentFlashbarContainer.parentFlashbar) }
|
||||
}
|
||||
|
||||
internal fun setTitle(title: String?) {
|
||||
if (TextUtils.isEmpty(title)) return
|
||||
|
||||
this.fbTitle.text = title
|
||||
this.fbTitle.visibility = VISIBLE
|
||||
}
|
||||
|
||||
internal fun setTitleSpanned(title: Spanned?) {
|
||||
if (title == null) return
|
||||
|
||||
this.fbTitle.text = title
|
||||
this.fbTitle.visibility = VISIBLE
|
||||
}
|
||||
|
||||
internal fun setTitleTypeface(typeface: Typeface?) {
|
||||
if (typeface == null) return
|
||||
fbTitle.typeface = typeface
|
||||
}
|
||||
|
||||
internal fun setTitleSizeInPx(size: Float?) {
|
||||
if (size == null) return
|
||||
fbTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, size)
|
||||
}
|
||||
|
||||
internal fun setTitleSizeInSp(size: Float?) {
|
||||
if (size == null) return
|
||||
fbTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, size)
|
||||
}
|
||||
|
||||
internal fun setTitleColor(color: Int?) {
|
||||
if (color == null) return
|
||||
fbTitle.setTextColor(color)
|
||||
}
|
||||
|
||||
internal fun setTitleAppearance(titleAppearance: Int?) {
|
||||
if (titleAppearance == null) return
|
||||
this.fbTitle.setTextAppearance(titleAppearance)
|
||||
}
|
||||
|
||||
internal fun setMessage(message: String?) {
|
||||
if (TextUtils.isEmpty(message)) return
|
||||
|
||||
this.fbMessage.text = message
|
||||
this.fbMessage.visibility = VISIBLE
|
||||
}
|
||||
|
||||
internal fun setMessageSpanned(message: Spanned?) {
|
||||
if (message == null) return
|
||||
|
||||
this.fbMessage.text = message
|
||||
this.fbMessage.visibility = VISIBLE
|
||||
}
|
||||
|
||||
internal fun setMessageTypeface(typeface: Typeface?) {
|
||||
if (typeface == null) return
|
||||
this.fbMessage.typeface = typeface
|
||||
}
|
||||
|
||||
internal fun setMessageSizeInPx(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbMessage.setTextSize(TypedValue.COMPLEX_UNIT_PX, size)
|
||||
}
|
||||
|
||||
internal fun setMessageSizeInSp(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbMessage.setTextSize(TypedValue.COMPLEX_UNIT_SP, size)
|
||||
}
|
||||
|
||||
internal fun setMessageColor(color: Int?) {
|
||||
if (color == null) return
|
||||
this.fbMessage.setTextColor(color)
|
||||
}
|
||||
|
||||
internal fun setMessageAppearance(messageAppearance: Int?) {
|
||||
if (messageAppearance == null) return
|
||||
this.fbMessage.setTextAppearance(messageAppearance)
|
||||
}
|
||||
|
||||
internal fun setPrimaryActionText(text: String?) {
|
||||
if (TextUtils.isEmpty(text)) return
|
||||
|
||||
this.fbPrimaryAction.text = text
|
||||
this.fbPrimaryAction.visibility = VISIBLE
|
||||
}
|
||||
|
||||
internal fun setPrimaryActionTextSpanned(text: Spanned?) {
|
||||
if (text == null) return
|
||||
|
||||
this.fbPrimaryAction.text = text
|
||||
this.fbPrimaryAction.visibility = VISIBLE
|
||||
}
|
||||
|
||||
internal fun setPrimaryActionTextTypeface(typeface: Typeface?) {
|
||||
if (typeface == null) return
|
||||
this.fbPrimaryAction.typeface = typeface
|
||||
}
|
||||
|
||||
internal fun setPrimaryActionTextSizeInPx(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbPrimaryAction.setTextSize(TypedValue.COMPLEX_UNIT_PX, size)
|
||||
}
|
||||
|
||||
internal fun setPrimaryActionTextSizeInSp(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbPrimaryAction.setTextSize(TypedValue.COMPLEX_UNIT_SP, size)
|
||||
}
|
||||
|
||||
internal fun setPrimaryActionTextColor(color: Int?) {
|
||||
if (color == null) return
|
||||
this.fbPrimaryAction.setTextColor(color)
|
||||
}
|
||||
|
||||
internal fun setPrimaryActionTextAppearance(messageAppearance: Int?) {
|
||||
if (messageAppearance == null) return
|
||||
this.fbPrimaryAction.setTextAppearance(messageAppearance)
|
||||
}
|
||||
|
||||
internal fun setPrimaryActionTapListener(listener: OnActionTapListener?) {
|
||||
if (listener == null) return
|
||||
|
||||
this.fbPrimaryAction.setOnClickListener {
|
||||
listener.onActionTapped(parentFlashbarContainer.parentFlashbar)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun setPositiveActionText(text: String?) {
|
||||
if (TextUtils.isEmpty(text)) return
|
||||
|
||||
this.fbSecondaryActionContainer.visibility = VISIBLE
|
||||
this.fbPositiveAction.text = text
|
||||
this.fbPositiveAction.visibility = VISIBLE
|
||||
}
|
||||
|
||||
internal fun setPositiveActionTextSpanned(text: Spanned?) {
|
||||
if (text == null) return
|
||||
|
||||
this.fbSecondaryActionContainer.visibility = VISIBLE
|
||||
this.fbPositiveAction.text = text
|
||||
this.fbPositiveAction.visibility = VISIBLE
|
||||
}
|
||||
|
||||
internal fun setPositiveActionTextTypeface(typeface: Typeface?) {
|
||||
if (typeface == null) return
|
||||
this.fbPositiveAction.typeface = typeface
|
||||
}
|
||||
|
||||
internal fun setPositiveActionTextSizeInPx(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbPositiveAction.setTextSize(TypedValue.COMPLEX_UNIT_PX, size)
|
||||
}
|
||||
|
||||
internal fun setPositiveActionTextSizeInSp(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbPositiveAction.setTextSize(TypedValue.COMPLEX_UNIT_SP, size)
|
||||
}
|
||||
|
||||
internal fun setPositiveActionTextColor(color: Int?) {
|
||||
if (color == null) return
|
||||
this.fbPositiveAction.setTextColor(color)
|
||||
}
|
||||
|
||||
internal fun setPositiveActionTextAppearance(messageAppearance: Int?) {
|
||||
if (messageAppearance == null) return
|
||||
this.fbPositiveAction.setTextAppearance(messageAppearance)
|
||||
}
|
||||
|
||||
internal fun setPositiveActionTapListener(listener: OnActionTapListener?) {
|
||||
if (listener == null) return
|
||||
|
||||
this.fbPositiveAction.setOnClickListener {
|
||||
listener.onActionTapped(parentFlashbarContainer.parentFlashbar)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun setNegativeActionText(text: String?) {
|
||||
if (TextUtils.isEmpty(text)) return
|
||||
|
||||
this.fbSecondaryActionContainer.visibility = VISIBLE
|
||||
this.fbNegativeAction.text = text
|
||||
this.fbNegativeAction.visibility = VISIBLE
|
||||
}
|
||||
|
||||
internal fun setNegativeActionTextSpanned(text: Spanned?) {
|
||||
if (text == null) return
|
||||
|
||||
this.fbSecondaryActionContainer.visibility = VISIBLE
|
||||
this.fbNegativeAction.text = text
|
||||
this.fbNegativeAction.visibility = VISIBLE
|
||||
}
|
||||
|
||||
internal fun setNegativeActionTextTypeface(typeface: Typeface?) {
|
||||
if (typeface == null) return
|
||||
this.fbNegativeAction.typeface = typeface
|
||||
}
|
||||
|
||||
internal fun setNegativeActionTextSizeInPx(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbNegativeAction.setTextSize(TypedValue.COMPLEX_UNIT_PX, size)
|
||||
}
|
||||
|
||||
internal fun setNegativeActionTextSizeInSp(size: Float?) {
|
||||
if (size == null) return
|
||||
this.fbNegativeAction.setTextSize(TypedValue.COMPLEX_UNIT_SP, size)
|
||||
}
|
||||
|
||||
internal fun setNegativeActionTextColor(color: Int?) {
|
||||
if (color == null) return
|
||||
this.fbNegativeAction.setTextColor(color)
|
||||
}
|
||||
|
||||
internal fun setNegativeActionTextAppearance(messageAppearance: Int?) {
|
||||
if (messageAppearance == null) return
|
||||
this.fbNegativeAction.setTextAppearance(messageAppearance)
|
||||
}
|
||||
|
||||
internal 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
|
||||
}
|
||||
|
||||
internal fun setIconDrawable(icon: Drawable?) {
|
||||
if (icon == null) return
|
||||
this.fbIcon.setImageDrawable(icon)
|
||||
}
|
||||
|
||||
internal fun setIconBitmap(bitmap: Bitmap?) {
|
||||
if (bitmap == null) return
|
||||
this.fbIcon.setImageBitmap(bitmap)
|
||||
}
|
||||
|
||||
internal 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))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun setProgressPosition(position: ProgressPosition?) {
|
||||
if (position == null) return
|
||||
when (position) {
|
||||
LEFT -> {
|
||||
fbLeftProgress.visibility = VISIBLE
|
||||
fbRightProgress.visibility = GONE
|
||||
}
|
||||
RIGHT -> {
|
||||
fbLeftProgress.visibility = GONE
|
||||
fbRightProgress.visibility = VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun setProgressTint(progressTint: Int?, position: ProgressPosition?) {
|
||||
if (position == null || progressTint == null) return
|
||||
|
||||
val progressBar =
|
||||
when (position) {
|
||||
LEFT -> fbLeftProgress
|
||||
RIGHT -> fbRightProgress
|
||||
}
|
||||
|
||||
progressBar.setBarColor(progressTint)
|
||||
}
|
||||
|
||||
private fun castShadow(shadowType: ShadowView.ShadowType, strength: Int) {
|
||||
val params = RelativeLayout.LayoutParams(MATCH_PARENT, context.convertDpToPx(strength))
|
||||
val shadow = ShadowView(context)
|
||||
shadow.applyShadow(shadowType)
|
||||
addView(shadow, params)
|
||||
}
|
||||
}
|
||||
+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)
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
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) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
} else {
|
||||
viewTreeObserver.removeGlobalOnLayoutListener(this)
|
||||
}
|
||||
f()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
internal enum class NavigationBarPosition {
|
||||
BOTTOM,
|
||||
RIGHT,
|
||||
LEFT,
|
||||
TOP
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.itsaky.androidide.flashbar.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import com.itsaky.androidide.flashbar.R
|
||||
|
||||
internal class FbButton : AppCompatTextView {
|
||||
constructor(context: Context) : this(context, null)
|
||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, R.style.FbButtonStyle)
|
||||
|
||||
constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet?,
|
||||
defStyleAttr: Int
|
||||
) : super(context, attrs, defStyleAttr)
|
||||
}
|
||||
@@ -0,0 +1,581 @@
|
||||
package com.itsaky.androidide.flashbar.view
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.os.SystemClock
|
||||
import android.provider.Settings
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import com.itsaky.androidide.flashbar.R
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/** Forked from Todd Davies' Progress Wheel https://github.com/Todd-Davies/ProgressWheel */
|
||||
class FbProgress : View {
|
||||
|
||||
private val barLength = 16
|
||||
private val barMaxLength = 270
|
||||
private val pauseGrowingTime: Long = 200
|
||||
|
||||
private var circleRadius = 28
|
||||
private var barWidth = 4
|
||||
private var rimWidth = 4
|
||||
private var fillRadius = false
|
||||
private var timeStartGrowing = 0.0
|
||||
private var barSpinCycleTime = 460.0
|
||||
private var barExtraLength = 0f
|
||||
private var barGrowingFromFront = true
|
||||
private var pausedTimeWithoutGrowing: Long = 0
|
||||
|
||||
private var barColor = -0x56000000
|
||||
private var rimColor = 0x00FFFFFF
|
||||
|
||||
private val barPaint = Paint()
|
||||
private val rimPaint = Paint()
|
||||
|
||||
private var circleBounds = RectF()
|
||||
|
||||
private var spinSpeed = 230.0f
|
||||
private var lastTimeAnimated: Long = 0
|
||||
private var linearProgress: Boolean = false
|
||||
|
||||
private var progress = 0.0f
|
||||
private var targetProgress = 0.0f
|
||||
private var isSpinning = false
|
||||
private var shouldAnimate: Boolean = false
|
||||
|
||||
private var callback: ProgressCallback? = null
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
parseAttributes(context.obtainStyledAttributes(attrs, R.styleable.FbProgress))
|
||||
setAnimationEnabled()
|
||||
}
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
setAnimationEnabled()
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
val viewWidth = circleRadius + this.paddingLeft + this.paddingRight
|
||||
val viewHeight = circleRadius + this.paddingTop + this.paddingBottom
|
||||
|
||||
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
|
||||
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
|
||||
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
|
||||
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
|
||||
|
||||
val width: Int =
|
||||
when (widthMode) {
|
||||
MeasureSpec.EXACTLY -> widthSize
|
||||
MeasureSpec.AT_MOST -> viewWidth.coerceAtMost(widthSize)
|
||||
else -> viewWidth
|
||||
}
|
||||
|
||||
val height: Int =
|
||||
when {
|
||||
heightMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.EXACTLY -> heightSize
|
||||
heightMode == MeasureSpec.AT_MOST -> viewHeight.coerceAtMost(heightSize)
|
||||
else -> viewHeight
|
||||
}
|
||||
|
||||
setMeasuredDimension(width, height)
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldW: Int, oldH: Int) {
|
||||
super.onSizeChanged(w, h, oldW, oldH)
|
||||
|
||||
setupBounds(w, h)
|
||||
setupPaints()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
canvas.drawArc(circleBounds, 360f, 360f, false, rimPaint)
|
||||
|
||||
var mustInvalidate = false
|
||||
|
||||
if (!shouldAnimate) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isSpinning) {
|
||||
mustInvalidate = true
|
||||
|
||||
val deltaTime = SystemClock.uptimeMillis() - lastTimeAnimated
|
||||
val deltaNormalized = deltaTime * spinSpeed / 1000.0f
|
||||
|
||||
updateBarLength(deltaTime)
|
||||
|
||||
progress += deltaNormalized
|
||||
if (progress > 360) {
|
||||
progress -= 360f
|
||||
|
||||
runCallback(-1.0f)
|
||||
}
|
||||
lastTimeAnimated = SystemClock.uptimeMillis()
|
||||
|
||||
var from = progress - 90
|
||||
var length = barLength + barExtraLength
|
||||
|
||||
if (isInEditMode) {
|
||||
from = 0f
|
||||
length = 135f
|
||||
}
|
||||
canvas.drawArc(circleBounds, from, length, false, barPaint)
|
||||
} else {
|
||||
val oldProgress = progress
|
||||
|
||||
if (progress != targetProgress) {
|
||||
mustInvalidate = true
|
||||
|
||||
val deltaTime = (SystemClock.uptimeMillis() - lastTimeAnimated).toFloat() / 1000
|
||||
val deltaNormalized = deltaTime * spinSpeed
|
||||
|
||||
progress = (progress + deltaNormalized).coerceAtMost(targetProgress)
|
||||
lastTimeAnimated = SystemClock.uptimeMillis()
|
||||
}
|
||||
|
||||
if (oldProgress != progress) {
|
||||
runCallback()
|
||||
}
|
||||
|
||||
var offset = 0.0f
|
||||
var progress = this.progress
|
||||
if (!linearProgress) {
|
||||
val factor = 2.0f
|
||||
offset =
|
||||
(1.0f - (1.0f - this.progress / 360.0f).toDouble().pow((2.0f * factor).toDouble()))
|
||||
.toFloat() * 360.0f
|
||||
progress =
|
||||
(1.0f - (1.0f - this.progress / 360.0f).toDouble().pow(factor.toDouble())).toFloat() *
|
||||
360.0f
|
||||
}
|
||||
|
||||
if (isInEditMode) {
|
||||
progress = 360f
|
||||
}
|
||||
|
||||
canvas.drawArc(circleBounds, offset - 90, progress, false, barPaint)
|
||||
}
|
||||
|
||||
if (mustInvalidate) {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onVisibilityChanged(changedView: View, visibility: Int) {
|
||||
super.onVisibilityChanged(changedView, visibility)
|
||||
|
||||
if (visibility == VISIBLE) {
|
||||
lastTimeAnimated = SystemClock.uptimeMillis()
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onSaveInstanceState(): Parcelable {
|
||||
val superState = super.onSaveInstanceState()
|
||||
|
||||
val ss = WheelSavedState(superState)
|
||||
|
||||
ss.mProgress = this.progress
|
||||
ss.mTargetProgress = this.targetProgress
|
||||
ss.isSpinning = this.isSpinning
|
||||
ss.spinSpeed = this.spinSpeed
|
||||
ss.barWidth = this.barWidth
|
||||
ss.barColor = this.barColor
|
||||
ss.rimWidth = this.rimWidth
|
||||
ss.rimColor = this.rimColor
|
||||
ss.circleRadius = this.circleRadius
|
||||
ss.linearProgress = this.linearProgress
|
||||
ss.fillRadius = this.fillRadius
|
||||
|
||||
return ss
|
||||
}
|
||||
|
||||
public override fun onRestoreInstanceState(state: Parcelable) {
|
||||
if (state !is WheelSavedState) {
|
||||
super.onRestoreInstanceState(state)
|
||||
return
|
||||
}
|
||||
|
||||
super.onRestoreInstanceState(state.superState)
|
||||
|
||||
this.progress = state.mProgress
|
||||
this.targetProgress = state.mTargetProgress
|
||||
this.isSpinning = state.isSpinning
|
||||
this.spinSpeed = state.spinSpeed
|
||||
this.barWidth = state.barWidth
|
||||
this.barColor = state.barColor
|
||||
this.rimWidth = state.rimWidth
|
||||
this.rimColor = state.rimColor
|
||||
this.circleRadius = state.circleRadius
|
||||
this.linearProgress = state.linearProgress
|
||||
this.fillRadius = state.fillRadius
|
||||
|
||||
this.lastTimeAnimated = SystemClock.uptimeMillis()
|
||||
}
|
||||
|
||||
fun resetCount() {
|
||||
progress = 0.0f
|
||||
targetProgress = 0.0f
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun stopSpinning() {
|
||||
isSpinning = false
|
||||
progress = 0.0f
|
||||
targetProgress = 0.0f
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun spin() {
|
||||
lastTimeAnimated = SystemClock.uptimeMillis()
|
||||
isSpinning = true
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun setInstantProgress(update: Float) {
|
||||
var progressUpdate = update
|
||||
if (isSpinning) {
|
||||
progress = 0.0f
|
||||
isSpinning = false
|
||||
}
|
||||
|
||||
if (progressUpdate > 1.0f) {
|
||||
progressUpdate -= 1.0f
|
||||
} else if (progressUpdate < 0) {
|
||||
progressUpdate = 0f
|
||||
}
|
||||
|
||||
if (progressUpdate == targetProgress) {
|
||||
return
|
||||
}
|
||||
|
||||
targetProgress = (progressUpdate * 360.0f).coerceAtMost(360.0f)
|
||||
progress = targetProgress
|
||||
lastTimeAnimated = SystemClock.uptimeMillis()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun getProgress(): Float {
|
||||
return if (isSpinning) -1f else progress / 360.0f
|
||||
}
|
||||
|
||||
fun setProgress(update: Float) {
|
||||
var progress = update
|
||||
if (isSpinning) {
|
||||
this.progress = 0.0f
|
||||
isSpinning = false
|
||||
|
||||
runCallback()
|
||||
}
|
||||
|
||||
if (progress > 1.0f) {
|
||||
progress -= 1.0f
|
||||
} else if (progress < 0) {
|
||||
progress = 0f
|
||||
}
|
||||
|
||||
if (progress == targetProgress) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.progress == targetProgress) {
|
||||
lastTimeAnimated = SystemClock.uptimeMillis()
|
||||
}
|
||||
|
||||
targetProgress = (progress * 360.0f).coerceAtMost(360.0f)
|
||||
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun setLinearProgress(isLinear: Boolean) {
|
||||
linearProgress = isLinear
|
||||
if (!isSpinning) {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
fun getCircleRadius() = circleRadius
|
||||
|
||||
fun setCircleRadius(circleRadius: Int) {
|
||||
this.circleRadius = circleRadius
|
||||
if (!isSpinning) {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
fun getBarWidth() = barWidth
|
||||
|
||||
fun setBarWidth(barWidth: Int) {
|
||||
this.barWidth = barWidth
|
||||
if (!isSpinning) {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
fun getBarColor() = barColor
|
||||
|
||||
fun setBarColor(barColor: Int) {
|
||||
this.barColor = barColor
|
||||
setupPaints()
|
||||
if (!isSpinning) {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
fun getRimColor(): Int {
|
||||
return rimColor
|
||||
}
|
||||
|
||||
fun setRimColor(rimColor: Int) {
|
||||
this.rimColor = rimColor
|
||||
setupPaints()
|
||||
if (!isSpinning) {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
fun getSpinSpeed() = spinSpeed / 360.0f
|
||||
|
||||
fun setSpinSpeed(spinSpeed: Float) {
|
||||
this.spinSpeed = spinSpeed * 360.0f
|
||||
}
|
||||
|
||||
fun getRimWidth() = rimWidth
|
||||
|
||||
fun setRimWidth(rimWidth: Int) {
|
||||
this.rimWidth = rimWidth
|
||||
if (!isSpinning) {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
fun setCallback(progressCallback: ProgressCallback) {
|
||||
callback = progressCallback
|
||||
|
||||
if (!isSpinning) {
|
||||
runCallback()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAnimationEnabled() {
|
||||
val currentApiVersion = VERSION.SDK_INT
|
||||
|
||||
val animationValue =
|
||||
if (currentApiVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
Settings.Global.getFloat(
|
||||
context.contentResolver,
|
||||
Settings.Global.ANIMATOR_DURATION_SCALE,
|
||||
1f
|
||||
)
|
||||
} else {
|
||||
Settings.System.getFloat(
|
||||
context.contentResolver,
|
||||
Settings.System.ANIMATOR_DURATION_SCALE,
|
||||
1f
|
||||
)
|
||||
}
|
||||
|
||||
shouldAnimate = animationValue != 0f
|
||||
}
|
||||
|
||||
private fun setupBounds(layoutWidth: Int, layoutHeight: Int) {
|
||||
val paddingTop = paddingTop
|
||||
val paddingBottom = paddingBottom
|
||||
val paddingLeft = paddingLeft
|
||||
val paddingRight = paddingRight
|
||||
|
||||
if (!fillRadius) {
|
||||
val minValue =
|
||||
(layoutWidth - paddingLeft - paddingRight).coerceAtMost(
|
||||
layoutHeight - paddingBottom - paddingTop
|
||||
)
|
||||
|
||||
val circleDiameter = minValue.coerceAtMost(circleRadius * 2 - barWidth * 2)
|
||||
|
||||
val xOffset = (layoutWidth - paddingLeft - paddingRight - circleDiameter) / 2 + paddingLeft
|
||||
val yOffset = (layoutHeight - paddingTop - paddingBottom - circleDiameter) / 2 + paddingTop
|
||||
|
||||
circleBounds =
|
||||
RectF(
|
||||
(xOffset + barWidth).toFloat(),
|
||||
(yOffset + barWidth).toFloat(),
|
||||
(xOffset + circleDiameter - barWidth).toFloat(),
|
||||
(yOffset + circleDiameter - barWidth).toFloat()
|
||||
)
|
||||
} else {
|
||||
circleBounds =
|
||||
RectF(
|
||||
(paddingLeft + barWidth).toFloat(),
|
||||
(paddingTop + barWidth).toFloat(),
|
||||
(layoutWidth - paddingRight - barWidth).toFloat(),
|
||||
(layoutHeight - paddingBottom - barWidth).toFloat()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseAttributes(a: TypedArray) {
|
||||
val metrics = context.resources.displayMetrics
|
||||
barWidth =
|
||||
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, barWidth.toFloat(), metrics).toInt()
|
||||
rimWidth =
|
||||
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, rimWidth.toFloat(), metrics).toInt()
|
||||
circleRadius =
|
||||
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleRadius.toFloat(), metrics)
|
||||
.toInt()
|
||||
|
||||
circleRadius =
|
||||
a.getDimension(R.styleable.FbProgress_fbp_circleRadius, circleRadius.toFloat()).toInt()
|
||||
|
||||
fillRadius = a.getBoolean(R.styleable.FbProgress_fbp_fillRadius, false)
|
||||
|
||||
barWidth = a.getDimension(R.styleable.FbProgress_fbp_barWidth, barWidth.toFloat()).toInt()
|
||||
|
||||
rimWidth = a.getDimension(R.styleable.FbProgress_fbp_rimWidth, rimWidth.toFloat()).toInt()
|
||||
|
||||
val baseSpinSpeed = a.getFloat(R.styleable.FbProgress_fbp_spinSpeed, spinSpeed / 360.0f)
|
||||
spinSpeed = baseSpinSpeed * 360
|
||||
|
||||
barSpinCycleTime =
|
||||
a.getInt(R.styleable.FbProgress_fbp_barSpinCycleTime, barSpinCycleTime.toInt()).toDouble()
|
||||
|
||||
barColor = a.getColor(R.styleable.FbProgress_fbp_barColor, barColor)
|
||||
|
||||
rimColor = a.getColor(R.styleable.FbProgress_fbp_rimColor, rimColor)
|
||||
|
||||
linearProgress = a.getBoolean(R.styleable.FbProgress_fbp_linearProgress, false)
|
||||
|
||||
if (a.getBoolean(R.styleable.FbProgress_fbp_progressIndeterminate, false)) {
|
||||
spin()
|
||||
}
|
||||
|
||||
a.recycle()
|
||||
}
|
||||
|
||||
private fun setupPaints() {
|
||||
barPaint.color = barColor
|
||||
barPaint.isAntiAlias = true
|
||||
barPaint.style = Paint.Style.STROKE
|
||||
barPaint.strokeWidth = barWidth.toFloat()
|
||||
|
||||
rimPaint.color = rimColor
|
||||
rimPaint.isAntiAlias = true
|
||||
rimPaint.style = Paint.Style.STROKE
|
||||
rimPaint.strokeWidth = rimWidth.toFloat()
|
||||
}
|
||||
|
||||
private fun updateBarLength(deltaTimeInMilliSeconds: Long) {
|
||||
if (pausedTimeWithoutGrowing >= pauseGrowingTime) {
|
||||
timeStartGrowing += deltaTimeInMilliSeconds.toDouble()
|
||||
|
||||
if (timeStartGrowing > barSpinCycleTime) {
|
||||
timeStartGrowing -= barSpinCycleTime
|
||||
pausedTimeWithoutGrowing = 0
|
||||
barGrowingFromFront = !barGrowingFromFront
|
||||
}
|
||||
|
||||
val distance = cos((timeStartGrowing / barSpinCycleTime + 1) * Math.PI).toFloat() / 2 + 0.5f
|
||||
val destLength = (barMaxLength - barLength).toFloat()
|
||||
|
||||
if (barGrowingFromFront) {
|
||||
barExtraLength = distance * destLength
|
||||
} else {
|
||||
val newLength = destLength * (1 - distance)
|
||||
progress += barExtraLength - newLength
|
||||
barExtraLength = newLength
|
||||
}
|
||||
} else {
|
||||
pausedTimeWithoutGrowing += deltaTimeInMilliSeconds
|
||||
}
|
||||
}
|
||||
|
||||
private fun runCallback(value: Float) {
|
||||
if (callback != null) {
|
||||
callback!!.onProgressUpdate(value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun runCallback() {
|
||||
if (callback != null) {
|
||||
val normalizedProgress = (progress * 100 / 360.0f).roundToInt().toFloat() / 100
|
||||
callback!!.onProgressUpdate(normalizedProgress)
|
||||
}
|
||||
}
|
||||
|
||||
interface ProgressCallback {
|
||||
fun onProgressUpdate(progress: Float)
|
||||
}
|
||||
|
||||
internal class WheelSavedState : View.BaseSavedState {
|
||||
var mProgress: Float = 0.toFloat()
|
||||
var mTargetProgress: Float = 0.toFloat()
|
||||
var isSpinning: Boolean = false
|
||||
var spinSpeed: Float = 0.toFloat()
|
||||
var barWidth: Int = 0
|
||||
var barColor: Int = 0
|
||||
var rimWidth: Int = 0
|
||||
var rimColor: Int = 0
|
||||
var circleRadius: Int = 0
|
||||
var linearProgress: Boolean = false
|
||||
var fillRadius: Boolean = false
|
||||
|
||||
constructor(superState: Parcelable?) : super(superState)
|
||||
|
||||
private constructor(`in`: Parcel) : super(`in`) {
|
||||
this.mProgress = `in`.readFloat()
|
||||
this.mTargetProgress = `in`.readFloat()
|
||||
this.isSpinning = `in`.readByte().toInt() != 0
|
||||
this.spinSpeed = `in`.readFloat()
|
||||
this.barWidth = `in`.readInt()
|
||||
this.barColor = `in`.readInt()
|
||||
this.rimWidth = `in`.readInt()
|
||||
this.rimColor = `in`.readInt()
|
||||
this.circleRadius = `in`.readInt()
|
||||
this.linearProgress = `in`.readByte().toInt() != 0
|
||||
this.fillRadius = `in`.readByte().toInt() != 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||
super.writeToParcel(out, flags)
|
||||
out.writeFloat(this.mProgress)
|
||||
out.writeFloat(this.mTargetProgress)
|
||||
out.writeByte((if (isSpinning) 1 else 0).toByte())
|
||||
out.writeFloat(this.spinSpeed)
|
||||
out.writeInt(this.barWidth)
|
||||
out.writeInt(this.barColor)
|
||||
out.writeInt(this.rimWidth)
|
||||
out.writeInt(this.rimColor)
|
||||
out.writeInt(this.circleRadius)
|
||||
out.writeByte((if (linearProgress) 1 else 0).toByte())
|
||||
out.writeByte((if (fillRadius) 1 else 0).toByte())
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Suppress("unused")
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<WheelSavedState> =
|
||||
object : Parcelable.Creator<WheelSavedState> {
|
||||
override fun createFromParcel(`in`: Parcel): WheelSavedState {
|
||||
return WheelSavedState(`in`)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<WheelSavedState> {
|
||||
return newArray(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.itsaky.androidide.flashbar.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.flashbar.R
|
||||
import com.itsaky.androidide.flashbar.view.ShadowView.ShadowType.BOTTOM
|
||||
import com.itsaky.androidide.flashbar.view.ShadowView.ShadowType.TOP
|
||||
|
||||
internal class ShadowView
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
|
||||
View(context, attrs, defStyleAttr) {
|
||||
|
||||
internal fun applyShadow(type: ShadowType) {
|
||||
when (type) {
|
||||
TOP -> setShadow(R.drawable.shadow_top)
|
||||
BOTTOM -> setShadow(R.drawable.shadow_bottom)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setShadow(@DrawableRes id: Int) {
|
||||
val shadow = ContextCompat.getDrawable(context, id)
|
||||
this.background = shadow
|
||||
}
|
||||
|
||||
enum class ShadowType {
|
||||
TOP,
|
||||
BOTTOM
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
@@ -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,133 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout 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:id="@+id/fbRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/warm_grey"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/fb_content_padding_all">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/fbContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<com.itsaky.androidide.flashbar.view.FbProgress
|
||||
android:id="@+id/fbLeftProgress"
|
||||
style="@style/FbCircularIndeterminateProgressStyle"
|
||||
android:layout_width="@dimen/fb_progress_size"
|
||||
android:layout_height="@dimen/fb_progress_size"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="@dimen/fb_margin_progress"
|
||||
android:layout_marginRight="@dimen/fb_margin_progress"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/fbIcon"
|
||||
android:layout_width="@dimen/fb_icon_size"
|
||||
android:layout_height="@dimen/fb_icon_size"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="@dimen/fb_margin_right_icon"
|
||||
android:layout_marginRight="@dimen/fb_margin_right_icon"
|
||||
android:src="@drawable/ic_info"
|
||||
android:visibility="gone"
|
||||
app:tint="@color/white"
|
||||
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:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/fb_title_message_margin"
|
||||
android:textAppearance="@style/FbTitleTextStyle"
|
||||
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/FbMessageStyle"
|
||||
android:visibility="gone"
|
||||
tools:text="A quick brown fox jumps of the lazy dog."
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.itsaky.androidide.flashbar.view.FbButton
|
||||
android:id="@+id/fbPrimaryAction"
|
||||
style="@style/FbButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/FbButtonTextStyle"
|
||||
android:visibility="gone"
|
||||
tools:text="CLICK"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.itsaky.androidide.flashbar.view.FbProgress
|
||||
android:id="@+id/fbRightProgress"
|
||||
style="@style/FbCircularIndeterminateProgressStyle"
|
||||
android:layout_width="@dimen/fb_progress_size"
|
||||
android:layout_height="@dimen/fb_progress_size"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="@dimen/fb_margin_progress"
|
||||
android:layout_marginLeft="@dimen/fb_margin_progress"
|
||||
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.itsaky.androidide.flashbar.view.FbButton
|
||||
android:id="@+id/fbPositiveAction"
|
||||
style="@style/FbButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/FbButtonTextStyle"
|
||||
android:visibility="gone"
|
||||
tools:text="OK"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<com.itsaky.androidide.flashbar.view.FbButton
|
||||
android:id="@+id/fbNegativeAction"
|
||||
style="@style/FbButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/fb_margin_secondary_action"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:textAppearance="@style/FbButtonTextStyle"
|
||||
android:visibility="gone"
|
||||
tools:text="CANCEL"
|
||||
tools:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -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,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<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>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="FbTitleTextStyle" parent="TextAppearance.AppCompat">
|
||||
<item name="android:textColor">@color/white</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:textSize">@dimen/fb_title_text_size</item>
|
||||
</style>
|
||||
|
||||
<style name="FbMessageStyle" parent="TextAppearance.AppCompat">
|
||||
<item name="android:textColor">@color/white</item>
|
||||
<item name="android:textSize">@dimen/fb_message_text_size</item>
|
||||
</style>
|
||||
|
||||
<style name="FbButtonTextStyle" parent="TextAppearance.AppCompat">
|
||||
<item name="android:textColor">@color/white</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:textSize">@dimen/fb_button_text_size</item>
|
||||
</style>
|
||||
|
||||
<style name="FbButtonStyle">
|
||||
<item name="android:paddingLeft">8dp</item>
|
||||
<item name="android:paddingRight">8dp</item>
|
||||
<item name="android:paddingTop">4dp</item>
|
||||
<item name="android:paddingBottom">4dp</item>
|
||||
<item name="android:background">@drawable/fb_button_background_selector</item>
|
||||
<item name="android:clickable">true</item>
|
||||
<item name="android:focusable">true</item>
|
||||
</style>
|
||||
|
||||
<style name="FbCircularIndeterminateProgressStyle">
|
||||
<item name="fbp_barColor">@color/white</item>
|
||||
<item name="fbp_barWidth">2dp</item>
|
||||
<item name="fbp_progressIndeterminate">true</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -29,8 +29,8 @@ import com.itsaky.androidide.preferences.internal.fontSize
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE
|
||||
import com.itsaky.androidide.uidesigner.databinding.ActivityShowXmlBinding
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.flashInfo
|
||||
import com.itsaky.androidide.utils.jetbrainsMono
|
||||
import com.itsaky.toaster.toastInfo
|
||||
import io.github.rosemoe.sora.event.Unsubscribe
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
|
||||
@@ -44,7 +44,7 @@ class ShowXmlActivity : BaseIDEActivity() {
|
||||
private var editor: CodeEditor? = null
|
||||
private var unsubscribe: Unsubscribe? = null
|
||||
private var binding: ActivityShowXmlBinding? = null
|
||||
|
||||
|
||||
private val log = ILogger.newInstance("ShowXmlActivity")
|
||||
|
||||
companion object {
|
||||
@@ -67,13 +67,13 @@ class ShowXmlActivity : BaseIDEActivity() {
|
||||
editor.typefaceLineNumber = jetbrainsMono()
|
||||
editor.setText(intent?.getStringExtra(KEY_XML) ?: "")
|
||||
editor.setTextSize(fontSize)
|
||||
|
||||
|
||||
IDEColorSchemeProvider.readScheme(this) {
|
||||
if (it == null) {
|
||||
log.error("Unable to load color sheme")
|
||||
return@readScheme
|
||||
}
|
||||
|
||||
|
||||
editor.colorScheme = it
|
||||
editor.setEditorLanguage(XMLLanguage(this))
|
||||
}
|
||||
@@ -86,7 +86,7 @@ class ShowXmlActivity : BaseIDEActivity() {
|
||||
|
||||
this.binding?.copy?.setOnClickListener {
|
||||
this.editor?.copyText(false)
|
||||
toastInfo(R.string.copied)
|
||||
flashInfo(R.string.copied)
|
||||
}
|
||||
|
||||
return this.binding!!.root
|
||||
|
||||
Reference in New Issue
Block a user