mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 01:08:50 -06:00
introducing confetti animation using konfetti library
This commit is contained in:
@@ -129,6 +129,7 @@ dependencies {
|
|||||||
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
||||||
implementation("com.google.android.material:material:1.11.0")
|
implementation("com.google.android.material:material:1.11.0")
|
||||||
implementation("com.opencsv:opencsv:5.9")
|
implementation("com.opencsv:opencsv:5.9")
|
||||||
|
implementation("nl.dionsegijn:konfetti-xml:2.0.2")
|
||||||
implementation(project(":uhabits-core"))
|
implementation(project(":uhabits-core"))
|
||||||
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
|
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
|
||||||
kaptAndroidTest("com.google.dagger:dagger-compiler:$daggerVersion")
|
kaptAndroidTest("com.google.dagger:dagger-compiler:$daggerVersion")
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import android.os.Bundle
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
|
import android.widget.LinearLayout
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
import org.isoron.uhabits.HabitsApplication
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.R
|
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.databinding.CheckmarkPopupBinding
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
|
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
|
||||||
import org.isoron.uhabits.utils.sres
|
import org.isoron.uhabits.utils.sres
|
||||||
|
import org.isoron.uhabits.utils.showConfetti
|
||||||
|
|
||||||
class CheckmarkDialog : AppCompatDialogFragment() {
|
class CheckmarkDialog : AppCompatDialogFragment() {
|
||||||
var onToggle: (Int, String) -> Unit = { _, _ -> }
|
var onToggle: (Int, String) -> Unit = { _, _ -> }
|
||||||
@@ -64,6 +66,10 @@ class CheckmarkDialog : AppCompatDialogFragment() {
|
|||||||
val notes = view.notes.text.toString().trim()
|
val notes = view.notes.text.toString().trim()
|
||||||
onToggle(v, notes)
|
onToggle(v, notes)
|
||||||
requireDialog().dismiss()
|
requireDialog().dismiss()
|
||||||
|
val konfettiView = requireActivity().findViewById<LinearLayout>(R.id.konfettiLayout)
|
||||||
|
when (v) {
|
||||||
|
YES_MANUAL -> showConfetti(konfettiView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
view.yesBtn.setOnClickListener { onClick(YES_MANUAL) }
|
view.yesBtn.setOnClickListener { onClick(YES_MANUAL) }
|
||||||
view.noBtn.setOnClickListener { onClick(NO) }
|
view.noBtn.setOnClickListener { onClick(NO) }
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.view.LayoutInflater
|
|||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.LinearLayout
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
import org.isoron.uhabits.HabitsApplication
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
@@ -16,6 +17,7 @@ import org.isoron.uhabits.core.models.Entry
|
|||||||
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
|
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils
|
import org.isoron.uhabits.utils.InterfaceUtils
|
||||||
import org.isoron.uhabits.utils.requestFocusWithKeyboard
|
import org.isoron.uhabits.utils.requestFocusWithKeyboard
|
||||||
|
import org.isoron.uhabits.utils.showConfetti
|
||||||
import org.isoron.uhabits.utils.sres
|
import org.isoron.uhabits.utils.sres
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import java.text.DecimalFormatSymbols
|
import java.text.DecimalFormatSymbols
|
||||||
@@ -115,5 +117,11 @@ class NumberDialog : AppCompatDialogFragment() {
|
|||||||
val notes = view.notes.text.toString()
|
val notes = view.notes.text.toString()
|
||||||
onToggle(value, notes)
|
onToggle(value, notes)
|
||||||
requireDialog().dismiss()
|
requireDialog().dismiss()
|
||||||
|
val v = requireActivity().findViewById<LinearLayout>(R.id.konfettiLayout)
|
||||||
|
|
||||||
|
if (value > 0.0) {
|
||||||
|
showConfetti(v)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import org.isoron.uhabits.utils.addAtBottom
|
|||||||
import org.isoron.uhabits.utils.addAtTop
|
import org.isoron.uhabits.utils.addAtTop
|
||||||
import org.isoron.uhabits.utils.addBelow
|
import org.isoron.uhabits.utils.addBelow
|
||||||
import org.isoron.uhabits.utils.buildToolbar
|
import org.isoron.uhabits.utils.buildToolbar
|
||||||
|
import org.isoron.uhabits.utils.buildKonfettiView
|
||||||
import org.isoron.uhabits.utils.currentTheme
|
import org.isoron.uhabits.utils.currentTheme
|
||||||
import org.isoron.uhabits.utils.dim
|
import org.isoron.uhabits.utils.dim
|
||||||
import org.isoron.uhabits.utils.dp
|
import org.isoron.uhabits.utils.dp
|
||||||
@@ -69,6 +70,7 @@ class ListHabitsRootView @Inject constructor(
|
|||||||
val listView: HabitCardListView = habitCardListViewFactory.create()
|
val listView: HabitCardListView = habitCardListViewFactory.create()
|
||||||
val llEmpty = EmptyListView(context)
|
val llEmpty = EmptyListView(context)
|
||||||
val tbar = buildToolbar()
|
val tbar = buildToolbar()
|
||||||
|
val konfettiView = buildKonfettiView()
|
||||||
val progressBar = TaskProgressBar(context, runner)
|
val progressBar = TaskProgressBar(context, runner)
|
||||||
val hintView: HintView
|
val hintView: HintView
|
||||||
val header = HeaderView(context, preferences, midnightTimer)
|
val header = HeaderView(context, preferences, midnightTimer)
|
||||||
@@ -80,6 +82,7 @@ class ListHabitsRootView @Inject constructor(
|
|||||||
|
|
||||||
val rootView = RelativeLayout(context).apply {
|
val rootView = RelativeLayout(context).apply {
|
||||||
background = sres.getDrawable(R.attr.windowBackgroundColor)
|
background = sres.getDrawable(R.attr.windowBackgroundColor)
|
||||||
|
addAtTop(konfettiView)
|
||||||
addAtTop(tbar)
|
addAtTop(tbar)
|
||||||
addBelow(header, tbar)
|
addBelow(header, tbar)
|
||||||
addBelow(listView, header, height = MATCH_PARENT)
|
addBelow(listView, header, height = MATCH_PARENT)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import org.isoron.uhabits.core.preferences.Preferences
|
|||||||
import org.isoron.uhabits.inject.ActivityContext
|
import org.isoron.uhabits.inject.ActivityContext
|
||||||
import org.isoron.uhabits.utils.drawNotesIndicator
|
import org.isoron.uhabits.utils.drawNotesIndicator
|
||||||
import org.isoron.uhabits.utils.getFontAwesome
|
import org.isoron.uhabits.utils.getFontAwesome
|
||||||
|
import org.isoron.uhabits.utils.showConfetti
|
||||||
import org.isoron.uhabits.utils.sp
|
import org.isoron.uhabits.utils.sp
|
||||||
import org.isoron.uhabits.utils.sres
|
import org.isoron.uhabits.utils.sres
|
||||||
import org.isoron.uhabits.utils.toMeasureSpec
|
import org.isoron.uhabits.utils.toMeasureSpec
|
||||||
@@ -88,7 +89,7 @@ class CheckmarkButtonView(
|
|||||||
setOnLongClickListener(this)
|
setOnLongClickListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun performToggle() {
|
fun performToggle(v: View) {
|
||||||
value = Entry.nextToggleValue(
|
value = Entry.nextToggleValue(
|
||||||
value = value,
|
value = value,
|
||||||
isSkipEnabled = preferences.isSkipEnabled,
|
isSkipEnabled = preferences.isSkipEnabled,
|
||||||
@@ -96,12 +97,15 @@ class CheckmarkButtonView(
|
|||||||
)
|
)
|
||||||
onToggle(value, notes)
|
onToggle(value, notes)
|
||||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
|
when (value) {
|
||||||
|
YES_MANUAL -> showConfetti(v.rootView)
|
||||||
|
}
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
if (preferences.isShortToggleEnabled) {
|
if (preferences.isShortToggleEnabled) {
|
||||||
performToggle()
|
performToggle(v)
|
||||||
} else {
|
} else {
|
||||||
onEdit()
|
onEdit()
|
||||||
}
|
}
|
||||||
@@ -111,7 +115,7 @@ class CheckmarkButtonView(
|
|||||||
if (preferences.isShortToggleEnabled) {
|
if (preferences.isShortToggleEnabled) {
|
||||||
onEdit()
|
onEdit()
|
||||||
} else {
|
} else {
|
||||||
performToggle()
|
performToggle(v)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import android.view.ViewGroup
|
|||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.widget.LinearLayout
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM
|
import android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM
|
||||||
import android.widget.RelativeLayout.ALIGN_PARENT_TOP
|
import android.widget.RelativeLayout.ALIGN_PARENT_TOP
|
||||||
@@ -46,6 +47,10 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import com.google.android.material.snackbar.Snackbar
|
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.platform.gui.toInt
|
||||||
import org.isoron.uhabits.HabitsApplication
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.R
|
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.models.PaletteColor
|
||||||
import org.isoron.uhabits.core.ui.views.Theme
|
import org.isoron.uhabits.core.ui.views.Theme
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
fun RelativeLayout.addBelow(
|
fun RelativeLayout.addBelow(
|
||||||
view: View,
|
view: View,
|
||||||
@@ -77,7 +83,9 @@ fun RelativeLayout.addAtBottom(
|
|||||||
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
|
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
|
||||||
addRule(ALIGN_PARENT_BOTTOM)
|
addRule(ALIGN_PARENT_BOTTOM)
|
||||||
}
|
}
|
||||||
view.id = View.generateViewId()
|
if (view.id == null) {
|
||||||
|
view.id = View.generateViewId()
|
||||||
|
}
|
||||||
this.addView(view)
|
this.addView(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +97,10 @@ fun RelativeLayout.addAtTop(
|
|||||||
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
|
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
|
||||||
addRule(ALIGN_PARENT_TOP)
|
addRule(ALIGN_PARENT_TOP)
|
||||||
}
|
}
|
||||||
view.id = View.generateViewId()
|
|
||||||
|
if (view.id == null) {
|
||||||
|
view.id = View.generateViewId()
|
||||||
|
}
|
||||||
this.addView(view)
|
this.addView(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +109,28 @@ fun ViewGroup.buildToolbar(): Toolbar {
|
|||||||
return inflater.inflate(R.layout.toolbar, null) as 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<LinearLayout>(R.id.konfettiLayout)
|
||||||
|
val kv = view.findViewById<KonfettiView>(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) {
|
fun View.showMessage(msg: String) {
|
||||||
try {
|
try {
|
||||||
val snackbar = Snackbar.make(this, msg, Snackbar.LENGTH_SHORT)
|
val snackbar = Snackbar.make(this, msg, Snackbar.LENGTH_SHORT)
|
||||||
|
|||||||
29
uhabits-android/src/main/res/layout/konfetti.xml
Normal file
29
uhabits-android/src/main/res/layout/konfetti.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2013 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ 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
|
||||||
|
-->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/konfettiLayout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:focusable="false"
|
||||||
|
android:orientation="vertical" >
|
||||||
|
|
||||||
|
<nl.dionsegijn.konfetti.xml.KonfettiView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/konfettttiView"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
Reference in New Issue
Block a user