From e6d1e42004b1ddef2d0ec0f4bc77a72f5969eb6b Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Tue, 28 Mar 2023 19:17:10 +0530 Subject: [PATCH] feat: use Flashbar for project sync notification --- .../androidide/actions/file/SaveFileAction.kt | 38 ++-- .../activities/editor/BaseEditorActivity.kt | 46 ++-- .../itsaky/androidide/ui/MaterialBanner.java | 209 ------------------ app/src/main/res/layout/activity_editor.xml | 6 - common/build.gradle.kts | 2 +- .../androidide/utils/FlashbarActivityUtils.kt | 34 +-- resources/src/main/res/values/strings.xml | 4 + .../itsaky/androidide/flashbar/Flashbar.kt | 2 +- 8 files changed, 72 insertions(+), 269 deletions(-) delete mode 100755 app/src/main/java/com/itsaky/androidide/ui/MaterialBanner.java diff --git a/app/src/main/java/com/itsaky/androidide/actions/file/SaveFileAction.kt b/app/src/main/java/com/itsaky/androidide/actions/file/SaveFileAction.kt index 1b1d3873a..78b4593dc 100644 --- a/app/src/main/java/com/itsaky/androidide/actions/file/SaveFileAction.kt +++ b/app/src/main/java/com/itsaky/androidide/actions/file/SaveFileAction.kt @@ -21,9 +21,11 @@ import android.content.Context import androidx.core.content.ContextCompat import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.EditorRelatedAction +import com.itsaky.androidide.models.SaveResult import com.itsaky.androidide.projects.ProjectManager import com.itsaky.androidide.resources.R import com.itsaky.androidide.utils.ILogger +import com.itsaky.androidide.utils.flashError import com.itsaky.androidide.utils.flashSuccess /** @author Akash Yadav */ @@ -52,34 +54,40 @@ class SaveFileAction(context: Context) : EditorRelatedAction() { enabled = context.areFilesModified() } - override fun execAction(data: ActionData): Boolean { - val context = data.getActivity() ?: return false + override fun execAction(data: ActionData): ResultWrapper { + val context = data.getActivity() ?: return ResultWrapper() return try { // Cannot use context.saveAll() because this.execAction is called on non-UI thread // and saveAll call will result in UI actions - val result = context.saveAllResult() - - if (result.xmlSaved) { - ProjectManager.generateSources() - } - - if (result.gradleSaved) { - context.notifySyncNeeded() - } - true + ResultWrapper(context.saveAllResult()) } catch (error: Throwable) { log.error("Failed to save file", error) - false + ResultWrapper() } } override fun postExec(data: ActionData, result: Any) { - if (result is Boolean && result) { + if (result is ResultWrapper && result.result != null) { + val context = data.requireActivity() + + // show save notification before calling 'notifySyncNeeded' so that the file save notification + // does not overlap the sync notification flashSuccess(R.string.all_saved) + + val saveResult = result.result + if (saveResult.xmlSaved) { + ProjectManager.generateSources() + } + + if (saveResult.gradleSaved) { + context.notifySyncNeeded() + } } else { log.error("Failed to save file") - TODO("Create message in strings.xml") + flashError(R.string.save_failed) } } + + inner class ResultWrapper(val result: SaveResult? = null) } diff --git a/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt index c44686ecc..d5e237680 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt @@ -80,16 +80,19 @@ import com.itsaky.androidide.projects.ProjectManager.getProjectDirPath import com.itsaky.androidide.projects.ProjectManager.projectPath import com.itsaky.androidide.projects.builder.BuildService import com.itsaky.androidide.services.LogReceiver -import com.itsaky.androidide.ui.MaterialBanner import com.itsaky.androidide.ui.editor.CodeEditorView import com.itsaky.androidide.uidesigner.UIDesignerActivity import com.itsaky.androidide.utils.ActionMenuUtils.createMenu import com.itsaky.androidide.utils.ApkInstallationSessionCallback +import com.itsaky.androidide.utils.DURATION_INDEFINITE 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.flashError +import com.itsaky.androidide.utils.flashbarBuilder import com.itsaky.androidide.utils.resolveAttr +import com.itsaky.androidide.utils.showOnUiThread +import com.itsaky.androidide.utils.withIcon import com.itsaky.androidide.viewmodel.EditorViewModel import com.itsaky.androidide.xml.resources.ResourceTableRegistry import com.itsaky.androidide.xml.versions.ApiVersionsRegistry @@ -191,13 +194,11 @@ abstract class BaseEditorActivity : val packageName = onResult(this, intent) if (packageName != null) { Snackbar.make(binding.realContainer, string.msg_action_open_application, Snackbar.LENGTH_LONG) - .setAction(string.yes) { - tryLaunchApp(packageName) - } + .setAction(string.yes) { tryLaunchApp(packageName) } .show() } } - + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -385,13 +386,23 @@ abstract class BaseEditorActivity : fun notifySyncNeeded(onConfirm: () -> Unit) { val buildService = Lookup.DEFAULT.lookup(BuildService.KEY_BUILD_SERVICE) - if (buildService != null && !buildService.isBuildInProgress) { - getSyncBanner()?.apply { - setNegative(android.R.string.cancel, null) - setPositive(android.R.string.ok) { onConfirm() } - show() + if (buildService == null || buildService.isBuildInProgress) return + + flashbarBuilder( + duration = DURATION_INDEFINITE, + backgroundColor = resolveAttr(attr.colorSecondaryContainer), + messageColor = resolveAttr(attr.colorOnSecondaryContainer) + ) + .withIcon(drawable.ic_sync, colorFilter = resolveAttr(attr.colorOnSecondaryContainer)) + .message(string.msg_sync_needed) + .positiveActionText(string.btn_sync) + .positiveActionTapListener { + onConfirm() + it.dismiss() } - } + .negativeActionText(string.btn_ignore_changes) + .negativeActionTapListener { it.dismiss() } + .showOnUiThread() } open fun getFileTreeFragment(): FileTreeFragment? { @@ -406,7 +417,7 @@ abstract class BaseEditorActivity : viewModel.statusText = text viewModel.statusGravity = gravity } - + private fun tryLaunchApp(packageName: String) { val launchIntent = packageManager.getLaunchIntentForPackage(packageName) launchIntent?.let { @@ -629,17 +640,6 @@ abstract class BaseEditorActivity : builder.create().show() } - private fun getSyncBanner(): MaterialBanner? { - return binding.run { - return@run syncBanner - .setContentTextColor(resolveAttr(attr.colorOnPrimaryContainer)) - .setBannerBackgroundColor(resolveAttr(attr.colorPrimaryContainer)) - .setButtonTextColor(resolveAttr(attr.colorOnPrimaryContainer)) - .setIcon(drawable.ic_sync) - .setContentText(string.msg_sync_needed) - } - } - open fun installationSessionCallback(): SessionCallback { return ApkInstallationSessionCallback(this).also { installationCallback = it } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/MaterialBanner.java b/app/src/main/java/com/itsaky/androidide/ui/MaterialBanner.java deleted file mode 100755 index da32b0aea..000000000 --- a/app/src/main/java/com/itsaky/androidide/ui/MaterialBanner.java +++ /dev/null @@ -1,209 +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 . - * - **************************************************************************************/ - -package com.itsaky.androidide.ui; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.animation.Animation; -import android.view.animation.Transformation; - -import androidx.annotation.DrawableRes; -import androidx.annotation.StringRes; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.content.ContextCompat; - -import com.itsaky.androidide.databinding.LayoutBannerBinding; - -public class MaterialBanner extends CoordinatorLayout { - - private LayoutBannerBinding binding; - private OnPositiveClickListener positiveListener; - private OnNegativeClickListener negativeListener; - - private String contentText; - private String leftButtonText; - private String rightButtonText; - private Drawable iconDrawable; - private int backgroundColor; - private int contentTextColor; - private int buttonTextColor; - - public MaterialBanner(Context context) { - this(context, null); - } - - public MaterialBanner(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public MaterialBanner(Context context, AttributeSet attrs, int style) { - super(context, attrs, style); - init(); - } - - private void init() { - binding = LayoutBannerBinding.inflate(LayoutInflater.from(getContext()), this, true); - } - - public void show() { - this.binding.contentTextView.setText(contentText); - this.binding.contentIconView.setImageDrawable(iconDrawable); - this.binding.rightButton.setText(rightButtonText); - this.binding.leftButton.setText(leftButtonText); - this.binding.getRoot().setBackgroundColor(backgroundColor); - this.binding.contentTextView.setTextColor(contentTextColor); - this.binding.leftButton.setTextColor(buttonTextColor); - this.binding.rightButton.setTextColor(buttonTextColor); - this.binding.rightButton.setOnClickListener( - v -> { - if (positiveListener == null) dismiss(); - else { - positiveListener.onClick(MaterialBanner.this); - dismiss(); - } - }); - this.binding.leftButton.setOnClickListener( - v -> { - if (negativeListener == null) dismiss(); - else { - negativeListener.onClick(MaterialBanner.this); - dismiss(); - } - }); - - final ConstraintLayout root = this.binding.getRoot(); - root.measure(-1, -2); - final int height = root.getMeasuredHeight(); - final LayoutParams p = (LayoutParams) root.getLayoutParams(); - p.height = 0; - root.setLayoutParams(p); - root.setVisibility(VISIBLE); - setVisibility(VISIBLE); - - Animation anim = - new Animation() { - @Override - public boolean willChangeBounds() { - return true; - } - - @Override - public void applyTransformation(float time, Transformation t) { - p.height = time == 1f ? -2 : (int) (height * time); - root.setLayoutParams(p); - root.requestLayout(); - } - }; - - anim.setDuration(200); - root.startAnimation(anim); - } - - public void dismiss() { - final ConstraintLayout root = this.binding.getRoot(); - final LayoutParams p = (LayoutParams) root.getLayoutParams(); - final int initialHeight = getMeasuredHeight(); - Animation animation = - new Animation() { - @Override - public boolean willChangeBounds() { - return true; - } - - @Override - public void applyTransformation(float interpolatedTime, Transformation t) { - if (interpolatedTime == 1f) { - root.setVisibility(GONE); - setVisibility(GONE); - } else { - p.height = (int) (initialHeight - (initialHeight * interpolatedTime)); - root.requestLayout(); - } - } - }; - animation.setDuration((long) (initialHeight / getResources().getDisplayMetrics().density)); - root.startAnimation(animation); - } - - public MaterialBanner setIcon(@DrawableRes int icon) { - return setIcon(ContextCompat.getDrawable(getContext(), icon)); - } - - public MaterialBanner setIcon(Drawable icon) { - this.iconDrawable = icon; - return this; - } - - public MaterialBanner setPositive(@StringRes int text, OnPositiveClickListener listener) { - return setPositive(getContext().getString(text), listener); - } - - public MaterialBanner setPositive(String text, OnPositiveClickListener listener) { - this.rightButtonText = text; - this.positiveListener = listener; - return this; - } - - public MaterialBanner setNegative(@StringRes int text, OnNegativeClickListener listener) { - return setNegative(getContext().getString(text), listener); - } - - public MaterialBanner setNegative(String text, OnNegativeClickListener listener) { - this.leftButtonText = text; - this.negativeListener = listener; - return this; - } - - public MaterialBanner setContentText(@StringRes int text) { - return setContentText(getContext().getString(text)); - } - - public MaterialBanner setContentText(String contentText) { - this.contentText = contentText; - return this; - } - - public MaterialBanner setBannerBackgroundColor(int backgroundColor) { - this.backgroundColor = backgroundColor; - return this; - } - - public MaterialBanner setContentTextColor(int contentTextColor) { - this.contentTextColor = contentTextColor; - return this; - } - - public MaterialBanner setButtonTextColor(int buttonTextColor) { - this.buttonTextColor = buttonTextColor; - return this; - } - - public static interface OnPositiveClickListener { - public void onClick(MaterialBanner banner); - } - - public static interface OnNegativeClickListener { - public void onClick(MaterialBanner banner); - } -} diff --git a/app/src/main/res/layout/activity_editor.xml b/app/src/main/res/layout/activity_editor.xml index 7879fc48f..2a3f3380d 100755 --- a/app/src/main/res/layout/activity_editor.xml +++ b/app/src/main/res/layout/activity_editor.xml @@ -32,12 +32,6 @@ android:layout_height="wrap_content" android:indeterminate="true" /> - - Close others Close this All files saved! + Failed to save files Destination: %s/ Drawable Layout @@ -539,4 +540,7 @@ Whether the editor should delete empty lines on a single backspace key event. Delete tab on backspace When enabled, the editor will delete spaces equal to the tab width when the backspace key is pressed. + + Sync + Ignore these changes diff --git a/subprojects/flashbar/src/main/java/com/itsaky/androidide/flashbar/Flashbar.kt b/subprojects/flashbar/src/main/java/com/itsaky/androidide/flashbar/Flashbar.kt index 71422996d..45992baa5 100644 --- a/subprojects/flashbar/src/main/java/com/itsaky/androidide/flashbar/Flashbar.kt +++ b/subprojects/flashbar/src/main/java/com/itsaky/androidide/flashbar/Flashbar.kt @@ -652,7 +652,7 @@ class Flashbar private constructor(private var builder: Builder) { RIGHT } - interface OnActionTapListener { + fun interface OnActionTapListener { fun onActionTapped(bar: Flashbar) }