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