diff --git a/app/build.gradle b/app/build.gradle
index b0866de1e4..702336cdac 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -220,7 +220,8 @@ dependencies {
implementation libs.disk.lru.cache
implementation libs.markwon
implementation libs.kizzyrpc
-
+ implementation libs.konfetti.xml
+ implementation libs.konfetti.core
implementation libs.conscrypt.android
implementation libs.sentry.android
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 62357e3251..f1ec24034a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -307,9 +307,6 @@
-
diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/core/github/AppUpdateRepository.kt b/app/src/main/kotlin/io/github/landwarderer/futon/core/github/AppUpdateRepository.kt
index 0deceba200..4aa90d5bce 100644
--- a/app/src/main/kotlin/io/github/landwarderer/futon/core/github/AppUpdateRepository.kt
+++ b/app/src/main/kotlin/io/github/landwarderer/futon/core/github/AppUpdateRepository.kt
@@ -2,82 +2,29 @@ package io.github.landwarderer.futon.core.github
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
-import io.github.landwarderer.futon.BuildConfig
import io.github.landwarderer.futon.R
import io.github.landwarderer.futon.core.network.BaseHttpClient
-import io.github.landwarderer.futon.core.prefs.AppSettings
import io.github.landwarderer.futon.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
-import org.json.JSONObject
import javax.inject.Inject
import javax.inject.Singleton
-private const val BUILD_TYPE_RELEASE = "release"
-
@Singleton
class AppUpdateRepository @Inject constructor(
- private val settings: AppSettings,
@BaseHttpClient private val okHttp: OkHttpClient,
@ApplicationContext context: Context,
) {
-// TODO("Fix update checking.")
- private val availableUpdate = MutableStateFlow(null)
- private val latestReleaseUrl = buildString {
- append("https://api.github.com/repos/")
- append(context.getString(R.string.github_updates_repo))
- append("/releases/latest")
- }
-
private val changelogUrl = buildString {
append("https://raw.githubusercontent.com/")
append(context.getString(R.string.github_updates_repo))
append("/refs/heads/devel/CHANGELOG.md")
}
- val isUpdateAvailable: Boolean
- get() = availableUpdate.value != null
-
- fun observeAvailableUpdate() = availableUpdate.asStateFlow()
-
- suspend fun fetchUpdate(): AppVersion? = withContext(Dispatchers.IO) {
- runCatchingCancellable {
- val request = Request.Builder()
- .get()
- .url(latestReleaseUrl)
- .build()
- val response = okHttp.newCall(request).await()
- val json = JSONObject(response.body?.string() ?: "{}")
-
- val currentVersion = VersionId(BuildConfig.VERSION_NAME)
- val releaseVersion = VersionId(json.getString("tag_name").removePrefix("v"))
-
- // Only return update if there's a newer version available
- if (releaseVersion <= currentVersion) {
- return@runCatchingCancellable null
- }
-
- AppVersion(
- id = json.getLong("id"),
- url = json.getString("html_url"),
- name = json.getString("name").removePrefix("v"),
- apkSize = 0L, // No longer downloading, so size not needed
- apkUrl = "", // No longer downloading
- description = json.getString("body"),
- )
- }.onFailure {
- it.printStackTraceDebug("AppUpdateRepository::fetchUpdate")
- }.onSuccess {
- availableUpdate.value = it
- }.getOrNull()
- }
-
suspend fun fetchChangelog(): String? = withContext(Dispatchers.IO) {
runCatchingCancellable {
val request = Request.Builder()
@@ -89,9 +36,4 @@ class AppUpdateRepository @Inject constructor(
it.printStackTraceDebug("AppUpdateRepository::fetchChangelog")
}.getOrNull()
}
-
- @Suppress("KotlinConstantConditions")
- suspend fun isUpdateSupported(): Boolean {
- return true // Updates are always available now (just checking for newer version)
- }
}
diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/core/nav/AppRouter.kt b/app/src/main/kotlin/io/github/landwarderer/futon/core/nav/AppRouter.kt
index 6c1fbd8f5a..a483076902 100644
--- a/app/src/main/kotlin/io/github/landwarderer/futon/core/nav/AppRouter.kt
+++ b/app/src/main/kotlin/io/github/landwarderer/futon/core/nav/AppRouter.kt
@@ -86,7 +86,6 @@ import io.github.landwarderer.futon.search.domain.SearchKind
import io.github.landwarderer.futon.search.ui.MangaListActivity
import io.github.landwarderer.futon.search.ui.multi.SearchActivity
import io.github.landwarderer.futon.settings.SettingsActivity
-import io.github.landwarderer.futon.settings.about.AppUpdateActivity
import io.github.landwarderer.futon.settings.override.OverrideConfigActivity
import io.github.landwarderer.futon.settings.reader.ReaderTapGridConfigActivity
import io.github.landwarderer.futon.settings.sources.auth.SourceAuthActivity
@@ -200,7 +199,6 @@ class AppRouter private constructor(
fun openBookmarks() = startActivity(AllBookmarksActivity::class.java)
- fun openAppUpdate() = startActivity(AppUpdateActivity::class.java)
fun openSuggestions() {
startActivity(suggestionsIntent(contextOrNull() ?: return))
diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/core/ui/dialog/ErrorDetailsDialog.kt b/app/src/main/kotlin/io/github/landwarderer/futon/core/ui/dialog/ErrorDetailsDialog.kt
index e4afcc6335..fd979d7268 100644
--- a/app/src/main/kotlin/io/github/landwarderer/futon/core/ui/dialog/ErrorDetailsDialog.kt
+++ b/app/src/main/kotlin/io/github/landwarderer/futon/core/ui/dialog/ErrorDetailsDialog.kt
@@ -8,7 +8,6 @@ import androidx.core.view.isVisible
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import io.github.landwarderer.futon.R
-import io.github.landwarderer.futon.core.github.AppUpdateRepository
import io.github.landwarderer.futon.core.nav.AppRouter
import io.github.landwarderer.futon.core.nav.router
import io.github.landwarderer.futon.core.ui.AlertDialogFragment
@@ -20,16 +19,12 @@ import io.github.landwarderer.futon.core.util.ext.report
import io.github.landwarderer.futon.core.util.ext.requireSerializable
import io.github.landwarderer.futon.core.util.ext.setTextAndVisible
import io.github.landwarderer.futon.databinding.DialogErrorDetailsBinding
-import javax.inject.Inject
@AndroidEntryPoint
class ErrorDetailsDialog : AlertDialogFragment(), View.OnClickListener {
private lateinit var exception: Throwable
- @Inject
- lateinit var appUpdateRepository: AppUpdateRepository
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val args = requireArguments()
@@ -48,9 +43,7 @@ class ErrorDetailsDialog : AlertDialogFragment(), Vie
binding.buttonBrowser.isVisible = isUrlAvailable
binding.textViewBrowser.isVisible = isUrlAvailable
binding.textViewDescription.setTextAndVisible(
- if (appUpdateRepository.isUpdateAvailable) {
- R.string.error_disclaimer_app_outdated
- } else if (exception.isReportable()) {
+ if (exception.isReportable()) {
R.string.error_disclaimer_report
} else {
0
@@ -67,12 +60,7 @@ class ErrorDetailsDialog : AlertDialogFragment(), Vie
.setNeutralButton(androidx.preference.R.string.copy) { _, _ ->
context?.copyToClipboard(getString(R.string.error), exception.stackTraceToString())
}
- if (appUpdateRepository.isUpdateAvailable) {
- builder.setPositiveButton(R.string.update) { _, _ ->
- router.openAppUpdate()
- dismiss()
- }
- } else if (exception.isReportable()) {
+ if (exception.isReportable()) {
builder.setPositiveButton(R.string.report) { _, _ ->
exception.report(silent = true)
dismiss()
diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/main/ui/MainActivity.kt b/app/src/main/kotlin/io/github/landwarderer/futon/main/ui/MainActivity.kt
index f68ad31fdc..d9f5c20648 100644
--- a/app/src/main/kotlin/io/github/landwarderer/futon/main/ui/MainActivity.kt
+++ b/app/src/main/kotlin/io/github/landwarderer/futon/main/ui/MainActivity.kt
@@ -154,7 +154,6 @@ class MainActivity : BaseActivity(), AppBarOwner, BottomNav
viewModel.isLoading.observe(this@MainActivity, this@MainActivity::onLoadingStateChanged)
viewModel.isResumeEnabled.observe(this@MainActivity, this@MainActivity::onResumeEnabledChanged)
viewModel.feedCounter.observe(this@MainActivity, ::onFeedCounterChanged)
- viewModel.appUpdate.observe(this@MainActivity, MenuInvalidator(this@MainActivity))
viewModel.onFirstStart.observeEvent(this@MainActivity) { router.showWelcomeSheet() }
viewModel.isBottomNavPinned.observe(this@MainActivity, ::setNavbarPinned)
searchSuggestionViewModel.isIncognitoModeEnabled.observe(this@MainActivity, this@MainActivity::onIncognitoModeChanged)
diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/main/ui/MainMenuProvider.kt b/app/src/main/kotlin/io/github/landwarderer/futon/main/ui/MainMenuProvider.kt
index 53a9647421..6900ad03a7 100644
--- a/app/src/main/kotlin/io/github/landwarderer/futon/main/ui/MainMenuProvider.kt
+++ b/app/src/main/kotlin/io/github/landwarderer/futon/main/ui/MainMenuProvider.kt
@@ -19,8 +19,7 @@ class MainMenuProvider(
override fun onPrepareMenu(menu: Menu) {
menu.findItem(R.id.action_incognito)?.isChecked =
viewModel.isIncognitoModeEnabled.value
- val hasAppUpdate = viewModel.appUpdate.value != null
- menu.findItem(R.id.action_app_update)?.isVisible = hasAppUpdate
+ menu.findItem(R.id.action_app_update)?.isVisible = false
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
@@ -34,11 +33,6 @@ class MainMenuProvider(
true
}
- R.id.action_app_update -> {
- router.openAppUpdate()
- true
- }
-
R.id.action_downloads -> {
router.openDownloads()
true
diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/main/ui/MainViewModel.kt b/app/src/main/kotlin/io/github/landwarderer/futon/main/ui/MainViewModel.kt
index fafbafc1cc..38e10c10a8 100644
--- a/app/src/main/kotlin/io/github/landwarderer/futon/main/ui/MainViewModel.kt
+++ b/app/src/main/kotlin/io/github/landwarderer/futon/main/ui/MainViewModel.kt
@@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import io.github.landwarderer.futon.core.exceptions.EmptyHistoryException
-import io.github.landwarderer.futon.core.github.AppUpdateRepository
import io.github.landwarderer.futon.core.prefs.AppSettings
import io.github.landwarderer.futon.core.prefs.observeAsFlow
import io.github.landwarderer.futon.core.prefs.observeAsStateFlow
@@ -25,7 +24,6 @@ import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(
private val historyRepository: HistoryRepository,
- private val appUpdateRepository: AppUpdateRepository,
trackingRepository: TrackingRepository,
private val settings: AppSettings,
readingResumeEnabledUseCase: ReadingResumeEnabledUseCase,
@@ -43,7 +41,6 @@ class MainViewModel @Inject constructor(
initialValue = false,
)
- val appUpdate = appUpdateRepository.observeAvailableUpdate()
val feedCounter = trackingRepository.observeUnreadUpdatesCount()
.withErrorHandling()
diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/AboutSettingsFragment.kt b/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/AboutSettingsFragment.kt
index 3e0d6295cf..487c596e00 100644
--- a/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/AboutSettingsFragment.kt
+++ b/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/AboutSettingsFragment.kt
@@ -1,30 +1,32 @@
package io.github.landwarderer.futon.settings.about
-import android.content.Intent
import android.os.Bundle
+import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.annotation.StringRes
+import androidx.core.content.ContextCompat
import androidx.fragment.app.viewModels
import androidx.preference.Preference
-import androidx.preference.SwitchPreferenceCompat
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.flow.combine
import io.github.landwarderer.futon.BuildConfig
import io.github.landwarderer.futon.R
-import io.github.landwarderer.futon.core.github.AppVersion
-import io.github.landwarderer.futon.core.github.VersionId
-import io.github.landwarderer.futon.core.github.isStable
import io.github.landwarderer.futon.core.nav.router
import io.github.landwarderer.futon.core.prefs.AppSettings
import io.github.landwarderer.futon.core.ui.BasePreferenceFragment
-import io.github.landwarderer.futon.core.util.ext.observe
-import io.github.landwarderer.futon.core.util.ext.observeEvent
+import nl.dionsegijn.konfetti.core.models.Shape
+import nl.dionsegijn.konfetti.xml.KonfettiView
+import nl.dionsegijn.konfetti.xml.image.DrawableImage
+import kotlin.random.Random
@AndroidEntryPoint
class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
private val viewModel by viewModels()
+ private var versionClickCount = 0
+ private lateinit var konfettiView: KonfettiView
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_about)
@@ -32,27 +34,50 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
title = getString(R.string.app_version, BuildConfig.VERSION_NAME)
}
findPreference(AppSettings.KEY_LINK_TELEGRAM)?.isVisible = false
- findPreference(AppSettings.KEY_UPDATES_UNSTABLE)?.run {
- isEnabled = VersionId(BuildConfig.VERSION_NAME).isStable
- if (!isEnabled) isChecked = true
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val list = super.onCreateView(inflater, container, savedInstanceState)
+ konfettiView = KonfettiView(requireContext()).apply {
+ layoutParams = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ // Ensure it doesn't consume clicks
+ isClickable = false
+ isFocusable = false
+ }
+ return FrameLayout(requireContext()).apply {
+ layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ addView(list)
+ addView(konfettiView)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- combine(viewModel.isUpdateSupported, viewModel.isLoading, ::Pair)
- .observe(viewLifecycleOwner) { (isUpdateSupported, isLoading) ->
- findPreference(AppSettings.KEY_UPDATES_UNSTABLE)?.isVisible = isUpdateSupported
- findPreference(AppSettings.KEY_APP_VERSION)?.isEnabled = isUpdateSupported && !isLoading
+ }
- }
- viewModel.onUpdateAvailable.observeEvent(viewLifecycleOwner, ::onUpdateAvailable)
+ override fun onDestroyView() {
+ (view as? ViewGroup)?.removeView(konfettiView)
+ super.onDestroyView()
}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
return when (preference.key) {
AppSettings.KEY_APP_VERSION -> {
- viewModel.checkForUpdates()
+ versionClickCount++
+ if (versionClickCount == 8) {
+ versionClickCount = 0
+ triggerEasterEgg()
+ }
true
}
@@ -82,12 +107,25 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
}
}
- private fun onUpdateAvailable(version: AppVersion?) {
- if (version == null) {
- Snackbar.make(listView, R.string.no_update_available, Snackbar.LENGTH_SHORT).show()
- } else {
- startActivity(Intent(requireContext(), AppUpdateActivity::class.java))
+ private fun triggerEasterEgg() {
+ val drawable = ContextCompat.getDrawable(requireContext(), R.drawable.unicorn)
+ if (drawable == null) {
+ Snackbar.make(listView, "Failed to load unicorn drawable", Snackbar.LENGTH_SHORT).show()
+ return
}
+
+ val coreImage = DrawableImage(drawable, drawable.intrinsicWidth, drawable.intrinsicHeight)
+ val drawableShape = Shape.DrawableShape(coreImage, tint = false, applyAlpha = true)
+
+ val presets = listOf(
+ Presets.festive(drawableShape),
+ Presets.explode(drawableShape),
+ Presets.parade(drawableShape),
+ Presets.rain(drawableShape)
+ )
+
+ val randomPreset = presets[Random.nextInt(presets.size)]
+ konfettiView.start(randomPreset)
}
private fun openLink(
diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/AboutSettingsViewModel.kt b/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/AboutSettingsViewModel.kt
index 14b9b26a5d..02b7f36a09 100644
--- a/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/AboutSettingsViewModel.kt
+++ b/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/AboutSettingsViewModel.kt
@@ -1,32 +1,10 @@
package io.github.landwarderer.futon.settings.about
-import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.stateIn
-import io.github.landwarderer.futon.core.github.AppUpdateRepository
-import io.github.landwarderer.futon.core.github.AppVersion
import io.github.landwarderer.futon.core.ui.BaseViewModel
-import io.github.landwarderer.futon.core.util.ext.MutableEventFlow
-import io.github.landwarderer.futon.core.util.ext.call
import javax.inject.Inject
@HiltViewModel
-class AboutSettingsViewModel @Inject constructor(
- private val appUpdateRepository: AppUpdateRepository,
-) : BaseViewModel() {
+class AboutSettingsViewModel @Inject constructor() : BaseViewModel() {
- val isUpdateSupported = flow {
- emit(appUpdateRepository.isUpdateSupported())
- }.stateIn(viewModelScope, SharingStarted.Eagerly, false)
-
- val onUpdateAvailable = MutableEventFlow()
-
- fun checkForUpdates() {
- launchLoadingJob {
- val update = appUpdateRepository.fetchUpdate()
- onUpdateAvailable.call(update)
- }
- }
}
diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/AppUpdateActivity.kt b/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/AppUpdateActivity.kt
deleted file mode 100644
index b42438fe3b..0000000000
--- a/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/AppUpdateActivity.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-package io.github.landwarderer.futon.settings.about
-
-import android.os.Bundle
-import android.view.View
-import android.view.ViewGroup.MarginLayoutParams
-import androidx.activity.viewModels
-import androidx.core.text.buildSpannedString
-import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.updateLayoutParams
-import androidx.core.view.updatePadding
-import com.google.android.material.snackbar.Snackbar
-import dagger.hilt.android.AndroidEntryPoint
-import io.noties.markwon.Markwon
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import io.github.landwarderer.futon.R
-import io.github.landwarderer.futon.core.github.AppVersion
-import io.github.landwarderer.futon.core.nav.router
-import io.github.landwarderer.futon.core.ui.BaseActivity
-import io.github.landwarderer.futon.core.util.ext.consumeAllSystemBarsInsets
-import io.github.landwarderer.futon.core.util.ext.getDisplayMessage
-import io.github.landwarderer.futon.core.util.ext.observe
-import io.github.landwarderer.futon.core.util.ext.observeEvent
-import io.github.landwarderer.futon.core.util.ext.setTextAndVisible
-import io.github.landwarderer.futon.core.util.ext.systemBarsInsets
-import io.github.landwarderer.futon.databinding.ActivityAppUpdateBinding
-
-@AndroidEntryPoint
-class AppUpdateActivity : BaseActivity(), View.OnClickListener {
-
- private val viewModel: AppUpdateViewModel by viewModels()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(ActivityAppUpdateBinding.inflate(layoutInflater))
- viewModel.nextVersion.observe(this, ::onNextVersionChanged)
- viewBinding.buttonCancel.setOnClickListener(this)
- viewBinding.buttonUpdate.setOnClickListener(this)
-
- viewModel.isLoading.observe(this) { isLoading ->
- viewBinding.buttonUpdate.isEnabled = viewModel.nextVersion.value != null && !isLoading
- }
- viewModel.onError.observeEvent(this, ::onError)
- }
-
- override fun onApplyWindowInsets(
- v: View,
- insets: WindowInsetsCompat
- ): WindowInsetsCompat {
- val barsInsets = insets.systemBarsInsets
- viewBinding.root.updatePadding(top = barsInsets.top)
- viewBinding.dockedToolbarChild.updateLayoutParams {
- leftMargin = barsInsets.left
- rightMargin = barsInsets.right
- bottomMargin = barsInsets.bottom
- }
- viewBinding.scrollView.updatePadding(
- left = barsInsets.left,
- right = barsInsets.right,
- )
- return insets.consumeAllSystemBarsInsets()
- }
-
- override fun onClick(v: View) {
- when (v.id) {
- R.id.button_cancel -> finishAfterTransition()
- R.id.button_update -> openGitHub()
- }
- }
-
- private suspend fun onNextVersionChanged(version: AppVersion?) {
- viewBinding.buttonUpdate.isEnabled = version != null && !viewModel.isLoading.value
- if (version == null) {
- viewBinding.textViewContent.setText(R.string.loading_)
- return
- }
- val markwon = Markwon.create(this)
- val message = withContext(Dispatchers.IO) {
- buildSpannedString {
- append(getString(R.string.new_version_s, version.name))
- appendLine()
- appendLine()
- append(markwon.toMarkdown(version.description))
- appendLine()
- appendLine()
- append(getString(R.string.github_download_warning))
- }
- }
- markwon.setParsedMarkdown(viewBinding.textViewContent, message)
- }
-
- private fun openGitHub() {
- val latestVersion = viewModel.nextVersion.value ?: return
- if (!router.openExternalBrowser(latestVersion.url, getString(R.string.open_in_browser))) {
- Snackbar.make(viewBinding.scrollView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
- }
- }
-
- private fun onError(e: Throwable) {
- viewBinding.textViewError.setTextAndVisible(R.string.error_occurred)
- }
-}
diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/AppUpdateViewModel.kt b/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/AppUpdateViewModel.kt
deleted file mode 100644
index fb495345bc..0000000000
--- a/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/AppUpdateViewModel.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package io.github.landwarderer.futon.settings.about
-
-import dagger.hilt.android.lifecycle.HiltViewModel
-import io.github.landwarderer.futon.core.github.AppUpdateRepository
-import io.github.landwarderer.futon.core.ui.BaseViewModel
-import javax.inject.Inject
-
-@HiltViewModel
-class AppUpdateViewModel @Inject constructor(
- private val repository: AppUpdateRepository,
-) : BaseViewModel() {
-
- val nextVersion = repository.observeAvailableUpdate()
-
- init {
- if (nextVersion.value == null) {
- launchLoadingJob {
- repository.fetchUpdate()
- }
- }
- }
-}
diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/Presets.kt b/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/Presets.kt
new file mode 100644
index 0000000000..cdefe6d8b5
--- /dev/null
+++ b/app/src/main/kotlin/io/github/landwarderer/futon/settings/about/Presets.kt
@@ -0,0 +1,114 @@
+package io.github.landwarderer.futon.settings.about
+
+import android.graphics.drawable.Drawable
+import nl.dionsegijn.konfetti.core.Angle
+import nl.dionsegijn.konfetti.core.Party
+import nl.dionsegijn.konfetti.core.Position
+import nl.dionsegijn.konfetti.core.Rotation
+import nl.dionsegijn.konfetti.core.Spread
+import nl.dionsegijn.konfetti.core.emitter.Emitter
+import nl.dionsegijn.konfetti.core.models.Shape
+import nl.dionsegijn.konfetti.core.models.Size
+import java.util.concurrent.TimeUnit
+
+class Presets {
+ companion object {
+ fun festive(drawable: Shape.DrawableShape? = null): List {
+ val party = Party(
+ speed = 30f,
+ maxSpeed = 50f,
+ damping = 0.9f,
+ angle = Angle.TOP,
+ spread = 45,
+ size = if (drawable != null) listOf(Size(24), Size(32)) else listOf(Size.SMALL, Size.MEDIUM),
+ shapes = if (drawable != null) listOf(drawable) else listOf(Shape.Square, Shape.Circle),
+ timeToLive = 3000L,
+ rotation = Rotation(multiplier3D = 0f),
+ colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def),
+ emitter = Emitter(duration = 100, TimeUnit.MILLISECONDS).max(30),
+ position = Position.Relative(0.5, 1.0)
+ )
+
+ return listOf(
+ party,
+ party.copy(
+ speed = 55f,
+ maxSpeed = 65f,
+ spread = 10,
+ emitter = Emitter(duration = 100, TimeUnit.MILLISECONDS).max(10),
+ ),
+ party.copy(
+ speed = 50f,
+ maxSpeed = 60f,
+ spread = 120,
+ emitter = Emitter(duration = 100, TimeUnit.MILLISECONDS).max(40),
+ ),
+ party.copy(
+ speed = 65f,
+ maxSpeed = 80f,
+ spread = 10,
+ emitter = Emitter(duration = 100, TimeUnit.MILLISECONDS).max(10),
+ )
+ )
+ }
+
+ fun explode(drawable: Shape.DrawableShape? = null): List {
+ return listOf(
+ Party(
+ speed = 0f,
+ maxSpeed = 30f,
+ damping = 0.9f,
+ spread = 360,
+ size = if (drawable != null) listOf(Size(24), Size(32)) else listOf(Size.SMALL, Size.MEDIUM),
+ shapes = if (drawable != null) listOf(drawable) else listOf(Shape.Square, Shape.Circle),
+ colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def),
+ emitter = Emitter(duration = 100, TimeUnit.MILLISECONDS).max(100),
+ rotation = Rotation(multiplier3D = 0f),
+ position = Position.Relative(0.5, 0.3)
+ )
+ )
+ }
+
+ fun parade(drawable: Shape.DrawableShape? = null): List {
+ val party = Party(
+ speed = 10f,
+ maxSpeed = 30f,
+ damping = 0.9f,
+ angle = Angle.RIGHT - 45,
+ spread = Spread.SMALL,
+ size = if (drawable != null) listOf(Size(24), Size(32)) else listOf(Size.SMALL, Size.MEDIUM),
+ shapes = if (drawable != null) listOf(drawable) else listOf(Shape.Square, Shape.Circle),
+ colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def),
+ emitter = Emitter(duration = 5, TimeUnit.SECONDS).perSecond(30),
+ rotation = Rotation(multiplier3D = 0f),
+ position = Position.Relative(0.0, 0.5)
+ )
+
+ return listOf(
+ party,
+ party.copy(
+ angle = party.angle - 90, // flip angle from right to left
+ position = Position.Relative(1.0, 0.5)
+ ),
+ )
+ }
+
+ fun rain(drawable: Shape.DrawableShape? = null): List {
+ return listOf(
+ Party(
+ speed = 0f,
+ maxSpeed = 15f,
+ damping = 0.9f,
+ angle = Angle.BOTTOM,
+ spread = Spread.ROUND,
+ size = if (drawable != null) listOf(Size(24), Size(32)) else listOf(Size.SMALL, Size.MEDIUM),
+ shapes = if (drawable != null) listOf(drawable) else listOf(Shape.Square, Shape.Circle),
+ colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def),
+ emitter = Emitter(duration = 5, TimeUnit.SECONDS).perSecond(100),
+ rotation = Rotation(multiplier3D = 0f),
+ position = Position.Relative(0.0, 0.0).between(Position.Relative(1.0, 0.0))
+ )
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/unicorn.png b/app/src/main/res/drawable/unicorn.png
new file mode 100644
index 0000000000..e95bfca72f
Binary files /dev/null and b/app/src/main/res/drawable/unicorn.png differ
diff --git a/app/src/main/res/layout/activity_app_update.xml b/app/src/main/res/layout/activity_app_update.xml
deleted file mode 100644
index 84f64f3038..0000000000
--- a/app/src/main/res/layout/activity_app_update.xml
+++ /dev/null
@@ -1,92 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/xml/pref_about.xml b/app/src/main/res/xml/pref_about.xml
index 78b51f4518..44ccb4d063 100644
--- a/app/src/main/res/xml/pref_about.xml
+++ b/app/src/main/res/xml/pref_about.xml
@@ -6,14 +6,7 @@
-
-
+ android:persistent="false" />