From a2ae1fdd49ff81a9156166eb40be16810469f808 Mon Sep 17 00:00:00 2001 From: Gokul K Date: Fri, 16 Jun 2023 19:01:56 +0530 Subject: [PATCH 1/5] introducing confetti animation using konfetti library --- uhabits-android/build.gradle.kts | 1 + .../common/dialogs/CheckmarkDialog.kt | 6 +++ .../activities/common/dialogs/NumberDialog.kt | 8 ++++ .../habits/list/ListHabitsRootView.kt | 3 ++ .../habits/list/views/CheckmarkButtonView.kt | 10 +++-- .../isoron/uhabits/utils/ViewExtensions.kt | 37 ++++++++++++++++++- .../src/main/res/layout/konfetti.xml | 29 +++++++++++++++ 7 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 uhabits-android/src/main/res/layout/konfetti.xml diff --git a/uhabits-android/build.gradle.kts b/uhabits-android/build.gradle.kts index 8e6d9947b..5e9adb694 100644 --- a/uhabits-android/build.gradle.kts +++ b/uhabits-android/build.gradle.kts @@ -115,6 +115,7 @@ dependencies { implementation("androidx.legacy:legacy-support-v4:1.0.0") implementation("com.google.android.material:material:1.8.0") implementation("com.opencsv:opencsv:5.7.1") + implementation("nl.dionsegijn:konfetti-xml:2.0.2") implementation(project(":uhabits-core")) kapt("com.google.dagger:dagger-compiler:$daggerVersion") kaptAndroidTest("com.google.dagger:dagger-compiler:$daggerVersion") diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/CheckmarkDialog.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/CheckmarkDialog.kt index 5df8ffd7b..bc1e8e4cb 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/CheckmarkDialog.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/CheckmarkDialog.kt @@ -24,6 +24,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View.GONE import android.view.View.VISIBLE +import android.widget.LinearLayout import androidx.appcompat.app.AppCompatDialogFragment import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.R @@ -34,6 +35,7 @@ import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL import org.isoron.uhabits.databinding.CheckmarkPopupBinding import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome import org.isoron.uhabits.utils.sres +import org.isoron.uhabits.utils.showConfetti class CheckmarkDialog : AppCompatDialogFragment() { var onToggle: (Int, String) -> Unit = { _, _ -> } @@ -64,6 +66,10 @@ class CheckmarkDialog : AppCompatDialogFragment() { val notes = view.notes.text.toString().trim() onToggle(v, notes) requireDialog().dismiss() + val konfettiView = requireActivity().findViewById(R.id.konfettiLayout) + when (v) { + YES_MANUAL -> showConfetti(konfettiView) + } } view.yesBtn.setOnClickListener { onClick(YES_MANUAL) } view.noBtn.setOnClickListener { onClick(NO) } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt index 7e10baa51..0890e3176 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt @@ -7,6 +7,7 @@ import android.view.KeyEvent import android.view.LayoutInflater import android.view.MotionEvent import android.view.View +import android.widget.LinearLayout import androidx.appcompat.app.AppCompatDialogFragment import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.R @@ -14,6 +15,7 @@ import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.databinding.CheckmarkPopupBinding import org.isoron.uhabits.utils.InterfaceUtils import org.isoron.uhabits.utils.requestFocusWithKeyboard +import org.isoron.uhabits.utils.showConfetti import org.isoron.uhabits.utils.sres import java.text.DecimalFormat import java.text.DecimalFormatSymbols @@ -100,5 +102,11 @@ class NumberDialog : AppCompatDialogFragment() { val notes = view.notes.text.toString() onToggle(value, notes) requireDialog().dismiss() + val v = requireActivity().findViewById(R.id.konfettiLayout) + + if (value > 0.0) { + showConfetti(v) + + } } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt index faa13173a..52ab3bc94 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt @@ -44,6 +44,7 @@ import org.isoron.uhabits.utils.addAtBottom import org.isoron.uhabits.utils.addAtTop import org.isoron.uhabits.utils.addBelow import org.isoron.uhabits.utils.buildToolbar +import org.isoron.uhabits.utils.buildKonfettiView import org.isoron.uhabits.utils.currentTheme import org.isoron.uhabits.utils.dim import org.isoron.uhabits.utils.dp @@ -69,6 +70,7 @@ class ListHabitsRootView @Inject constructor( val listView: HabitCardListView = habitCardListViewFactory.create() val llEmpty = EmptyListView(context) val tbar = buildToolbar() + val konfettiView = buildKonfettiView() val progressBar = TaskProgressBar(context, runner) val hintView: HintView val header = HeaderView(context, preferences, midnightTimer) @@ -80,6 +82,7 @@ class ListHabitsRootView @Inject constructor( val rootView = RelativeLayout(context).apply { background = sres.getDrawable(R.attr.windowBackgroundColor) + addAtTop(konfettiView) addAtTop(tbar) addBelow(header, tbar) addBelow(listView, header, height = MATCH_PARENT) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt index 39faa0283..891abb78d 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt @@ -39,6 +39,7 @@ import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.inject.ActivityContext import org.isoron.uhabits.utils.drawNotesIndicator import org.isoron.uhabits.utils.getFontAwesome +import org.isoron.uhabits.utils.showConfetti import org.isoron.uhabits.utils.sp import org.isoron.uhabits.utils.sres import org.isoron.uhabits.utils.toMeasureSpec @@ -88,7 +89,7 @@ class CheckmarkButtonView( setOnLongClickListener(this) } - fun performToggle() { + fun performToggle(v: View) { value = Entry.nextToggleValue( value = value, isSkipEnabled = preferences.isSkipEnabled, @@ -96,17 +97,20 @@ class CheckmarkButtonView( ) onToggle(value, notes) performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + when (value) { + YES_MANUAL -> showConfetti(v.rootView) + } invalidate() } override fun onClick(v: View) { - if (preferences.isShortToggleEnabled) performToggle() + if (preferences.isShortToggleEnabled) performToggle(v) else onEdit() } override fun onLongClick(v: View): Boolean { if (preferences.isShortToggleEnabled) onEdit() - else performToggle() + else performToggle(v) return true } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/utils/ViewExtensions.kt b/uhabits-android/src/main/java/org/isoron/uhabits/utils/ViewExtensions.kt index 35a8cecbd..e6768f14b 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/utils/ViewExtensions.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/utils/ViewExtensions.kt @@ -36,6 +36,7 @@ import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.WindowManager +import android.widget.LinearLayout import android.widget.RelativeLayout import android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM import android.widget.RelativeLayout.ALIGN_PARENT_TOP @@ -46,6 +47,10 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.core.content.FileProvider import com.google.android.material.snackbar.Snackbar +import nl.dionsegijn.konfetti.core.Party +import nl.dionsegijn.konfetti.core.Position +import nl.dionsegijn.konfetti.core.emitter.Emitter +import nl.dionsegijn.konfetti.xml.KonfettiView import org.isoron.platform.gui.toInt import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.R @@ -53,6 +58,7 @@ import org.isoron.uhabits.activities.AndroidThemeSwitcher import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.ui.views.Theme import java.io.File +import java.util.concurrent.TimeUnit fun RelativeLayout.addBelow( view: View, @@ -79,7 +85,9 @@ fun RelativeLayout.addAtBottom( view.layoutParams = RelativeLayout.LayoutParams(width, height).apply { addRule(ALIGN_PARENT_BOTTOM) } - view.id = View.generateViewId() + if (view.id == null) { + view.id = View.generateViewId() + } this.addView(view) } @@ -92,7 +100,10 @@ fun RelativeLayout.addAtTop( view.layoutParams = RelativeLayout.LayoutParams(width, height).apply { addRule(ALIGN_PARENT_TOP) } - view.id = View.generateViewId() + + if (view.id == null) { + view.id = View.generateViewId() + } this.addView(view) } @@ -101,6 +112,28 @@ fun ViewGroup.buildToolbar(): Toolbar { return inflater.inflate(R.layout.toolbar, null) as Toolbar } +fun ViewGroup.buildKonfettiView(): View { + val inflater = LayoutInflater.from(context) + return inflater.inflate(R.layout.konfetti, null) as View +} + +fun showConfetti(view: View) { + val viewId = R.id.konfettttiView + val linearLayout = view.findViewById(R.id.konfettiLayout) + val kv = view.findViewById(viewId) + linearLayout.bringToFront() + val party = Party( + speed = 0f, + maxSpeed = 32f, + damping = 0.9f, + spread = 360, + colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def, 0x818181, 0x81a48c), + position = Position.Relative(0.5, 0.3), + emitter = Emitter(duration = 300, TimeUnit.MILLISECONDS).max(300) + ) + kv.start(party) +} + fun View.showMessage(msg: String) { try { val snackbar = Snackbar.make(this, msg, Snackbar.LENGTH_SHORT) diff --git a/uhabits-android/src/main/res/layout/konfetti.xml b/uhabits-android/src/main/res/layout/konfetti.xml new file mode 100644 index 000000000..49bd60daa --- /dev/null +++ b/uhabits-android/src/main/res/layout/konfetti.xml @@ -0,0 +1,29 @@ + + + + + + + From 2fd7b447fb439ce2ff7ad83003a8105f6ba6eb42 Mon Sep 17 00:00:00 2001 From: Gokul K Date: Tue, 4 Jul 2023 18:50:28 +0530 Subject: [PATCH 2/5] fixing some comments from @hiqua --- .../uhabits/activities/common/dialogs/NumberDialog.kt | 6 +++--- .../activities/habits/list/views/CheckmarkButtonView.kt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt index 0890e3176..52dad9e99 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt @@ -102,11 +102,11 @@ class NumberDialog : AppCompatDialogFragment() { val notes = view.notes.text.toString() onToggle(value, notes) requireDialog().dismiss() - val v = requireActivity().findViewById(R.id.konfettiLayout) + val konfettiView = requireActivity().findViewById(R.id.konfettiLayout) if (value > 0.0) { - showConfetti(v) - + //To motivate, show confetti even if some value is present + showConfetti(konfettiView) } } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt index 891abb78d..6a5bd5a49 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt @@ -89,7 +89,7 @@ class CheckmarkButtonView( setOnLongClickListener(this) } - fun performToggle(v: View) { + fun performToggle(view: View) { value = Entry.nextToggleValue( value = value, isSkipEnabled = preferences.isSkipEnabled, @@ -98,7 +98,7 @@ class CheckmarkButtonView( onToggle(value, notes) performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) when (value) { - YES_MANUAL -> showConfetti(v.rootView) + YES_MANUAL -> showConfetti(view.rootView) } invalidate() } From 6901968a6404b1d816abbae571e8ab3e877c7549 Mon Sep 17 00:00:00 2001 From: Gokul K Date: Tue, 4 Jul 2023 20:53:26 +0530 Subject: [PATCH 3/5] fixed ktlint erorrs and is building successfully now --- .../isoron/uhabits/activities/common/dialogs/CheckmarkDialog.kt | 2 +- .../isoron/uhabits/activities/common/dialogs/NumberDialog.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/CheckmarkDialog.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/CheckmarkDialog.kt index bc1e8e4cb..93ffd2c80 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/CheckmarkDialog.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/CheckmarkDialog.kt @@ -34,8 +34,8 @@ import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL import org.isoron.uhabits.databinding.CheckmarkPopupBinding import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome -import org.isoron.uhabits.utils.sres import org.isoron.uhabits.utils.showConfetti +import org.isoron.uhabits.utils.sres class CheckmarkDialog : AppCompatDialogFragment() { var onToggle: (Int, String) -> Unit = { _, _ -> } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt index 52dad9e99..193b9009d 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt @@ -105,7 +105,7 @@ class NumberDialog : AppCompatDialogFragment() { val konfettiView = requireActivity().findViewById(R.id.konfettiLayout) if (value > 0.0) { - //To motivate, show confetti even if some value is present + // To motivate, show confetti even if some value is present showConfetti(konfettiView) } } From d64339749cf8c518e981262fbe3a82a5f938018b Mon Sep 17 00:00:00 2001 From: Gokul K Date: Wed, 5 Jul 2023 12:05:10 +0530 Subject: [PATCH 4/5] fixing another lint error --- .../isoron/uhabits/activities/habits/list/ListHabitsRootView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt index 52ab3bc94..0a7e98cc5 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt @@ -43,8 +43,8 @@ import org.isoron.uhabits.inject.ActivityScope import org.isoron.uhabits.utils.addAtBottom import org.isoron.uhabits.utils.addAtTop import org.isoron.uhabits.utils.addBelow -import org.isoron.uhabits.utils.buildToolbar import org.isoron.uhabits.utils.buildKonfettiView +import org.isoron.uhabits.utils.buildToolbar import org.isoron.uhabits.utils.currentTheme import org.isoron.uhabits.utils.dim import org.isoron.uhabits.utils.dp From 48c0b39451eccb3846383c9511bf2809fd10a0db Mon Sep 17 00:00:00 2001 From: Gokul K Date: Tue, 11 Jul 2023 18:48:55 +0530 Subject: [PATCH 5/5] rendering only if layout and konfetti view exists - to avoid null reference errors --- .../java/org/isoron/uhabits/utils/ViewExtensions.kt | 10 +++++++--- uhabits-android/src/main/res/layout/konfetti.xml | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/utils/ViewExtensions.kt b/uhabits-android/src/main/java/org/isoron/uhabits/utils/ViewExtensions.kt index e6768f14b..2a9e161f9 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/utils/ViewExtensions.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/utils/ViewExtensions.kt @@ -118,10 +118,12 @@ fun ViewGroup.buildKonfettiView(): View { } fun showConfetti(view: View) { - val viewId = R.id.konfettttiView + val viewId = R.id.konfetttiView val linearLayout = view.findViewById(R.id.konfettiLayout) val kv = view.findViewById(viewId) - linearLayout.bringToFront() + if (linearLayout != null) { + linearLayout.bringToFront() + } val party = Party( speed = 0f, maxSpeed = 32f, @@ -131,7 +133,9 @@ fun showConfetti(view: View) { position = Position.Relative(0.5, 0.3), emitter = Emitter(duration = 300, TimeUnit.MILLISECONDS).max(300) ) - kv.start(party) + if (kv != null) { + kv.start(party) + } } fun View.showMessage(msg: String) { diff --git a/uhabits-android/src/main/res/layout/konfetti.xml b/uhabits-android/src/main/res/layout/konfetti.xml index 49bd60daa..1f7f918dc 100644 --- a/uhabits-android/src/main/res/layout/konfetti.xml +++ b/uhabits-android/src/main/res/layout/konfetti.xml @@ -24,6 +24,6 @@ + android:id="@+id/konfetttiView"/>