From 46a699698255b1a3e68cb3a4b506a9d962701bc5 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Wed, 23 Mar 2022 00:04:27 -0500 Subject: [PATCH] Beast mode banner --- .../org/tasks/preferences/fragments/Debug.kt | 14 ++ app/src/debug/res/values/keys.xml | 1 + app/src/debug/res/xml/preferences_debug.xml | 4 + .../astrid/activity/TaskEditFragment.kt | 45 ++++++ .../astrid/activity/TaskListFragment.kt | 20 +-- app/src/main/java/org/tasks/compose/Banner.kt | 147 ++++++++++++------ .../java/org/tasks/preferences/Preferences.kt | 4 + .../main/res/layout/fragment_task_edit.xml | 5 + app/src/main/res/values/keys.xml | 1 + app/src/main/res/values/strings.xml | 2 + 10 files changed, 186 insertions(+), 57 deletions(-) diff --git a/app/src/debug/java/org/tasks/preferences/fragments/Debug.kt b/app/src/debug/java/org/tasks/preferences/fragments/Debug.kt index 6813c74e0..144acdc9e 100644 --- a/app/src/debug/java/org/tasks/preferences/fragments/Debug.kt +++ b/app/src/debug/java/org/tasks/preferences/fragments/Debug.kt @@ -5,6 +5,7 @@ import androidx.annotation.StringRes import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import at.bitfire.cert4android.CustomCertManager.Companion.resetCertificates +import com.todoroo.andlib.utility.DateUtilities.now import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import org.tasks.R @@ -12,13 +13,17 @@ import org.tasks.billing.BillingClient import org.tasks.billing.Inventory import org.tasks.extensions.Context.toast import org.tasks.injection.InjectingPreferenceFragment +import org.tasks.preferences.Preferences +import java.util.concurrent.TimeUnit import javax.inject.Inject +import kotlin.math.min @AndroidEntryPoint class Debug : InjectingPreferenceFragment() { @Inject lateinit var inventory: Inventory @Inject lateinit var billingClient: BillingClient + @Inject lateinit var preferences: Preferences override fun getPreferenceXml() = R.xml.preferences_debug @@ -54,6 +59,15 @@ class Debug : InjectingPreferenceFragment() { findPreference(R.string.debug_crash_app).setOnPreferenceClickListener { throw RuntimeException("Crashed app from debug preferences") } + + findPreference(R.string.debug_clear_hints).setOnPreferenceClickListener { + preferences.installDate = + min(preferences.installDate, now() - TimeUnit.DAYS.toMillis(14)) + preferences.lastSubscribeRequest = 0L + preferences.lastReviewRequest = 0L + preferences.shownBeastModeHint = false + true + } } private fun setupIap(@StringRes prefId: Int, sku: String) { diff --git a/app/src/debug/res/values/keys.xml b/app/src/debug/res/values/keys.xml index 819b809ea..a8f34c068 100644 --- a/app/src/debug/res/values/keys.xml +++ b/app/src/debug/res/values/keys.xml @@ -14,5 +14,6 @@ Crash app now Crash on violation Restart app + Clear hints com.googleusercontent.apps.1006257750459-vf4mvft1b3rfda8b4c4bl4k4418abqlf \ No newline at end of file diff --git a/app/src/debug/res/xml/preferences_debug.xml b/app/src/debug/res/xml/preferences_debug.xml index be7c6a376..47951dcda 100644 --- a/app/src/debug/res/xml/preferences_debug.xml +++ b/app/src/debug/res/xml/preferences_debug.xml @@ -49,4 +49,8 @@ + + \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.kt b/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.kt index 1b7a30e29..9c18159f0 100755 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.kt @@ -7,6 +7,7 @@ package com.todoroo.astrid.activity import android.app.Activity import android.content.Context +import android.content.Intent import android.graphics.Paint import android.net.Uri import android.os.Bundle @@ -16,7 +17,14 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.Toolbar +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.widget.addTextChangedListener import androidx.fragment.app.Fragment @@ -25,6 +33,7 @@ import androidx.lifecycle.lifecycleScope import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout.Behavior.DragCallback import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener +import com.google.android.material.composethemeadapter.MdcTheme import com.todoroo.andlib.utility.AndroidUtilities import com.todoroo.andlib.utility.DateUtilities import com.todoroo.astrid.api.Filter @@ -43,6 +52,7 @@ import kotlinx.coroutines.withContext import org.tasks.R import org.tasks.Strings.isNullOrEmpty import org.tasks.analytics.Firebase +import org.tasks.compose.BeastModeBanner import org.tasks.data.Alarm import org.tasks.data.Location import org.tasks.data.TagData @@ -225,6 +235,7 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener { .launchIn(viewLifecycleOwner.lifecycleScope) } + @OptIn(ExperimentalAnimationApi::class) private suspend fun process(event: TaskEditEvent) { when (event) { is TaskEditEvent.Discard -> @@ -234,6 +245,37 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener { } } + private val beastMode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + val transaction = childFragmentManager.beginTransaction() + taskEditControlSetFragmentManager.getOrCreateFragments(childFragmentManager).forEach { + transaction.remove(it) + } + transaction.commit() + activity?.recreate() + } + + @OptIn(ExperimentalAnimationApi::class) + private fun showBeastModeHint() { + binding.banner.setContent { + var visible by rememberSaveable { mutableStateOf(true) } + val context = LocalContext.current + MdcTheme { + BeastModeBanner( + visible, + showSettings = { + visible = false + preferences.shownBeastModeHint = true + beastMode.launch(Intent(context, BeastModePreferences::class.java)) + }, + dismiss = { + visible = false + preferences.shownBeastModeHint = true + } + ) + } + } + } + override fun onResume() { super.onResume() @@ -242,6 +284,9 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener { val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.showSoftInput(binding.title, InputMethodManager.SHOW_IMPLICIT) } + if (!preferences.shownBeastModeHint) { + showBeastModeHint() + } } override fun onMenuItemClick(item: MenuItem): Boolean { diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt index 16098920a..6ce39adec 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt @@ -24,8 +24,10 @@ import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.Toolbar import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.app.ShareCompat import androidx.core.view.forEach @@ -89,7 +91,7 @@ import org.tasks.activities.TagSettingsActivity import org.tasks.analytics.Firebase import org.tasks.billing.PurchaseActivity import org.tasks.caldav.BaseCaldavCalendarSettingsActivity -import org.tasks.compose.AnimatedBanner +import org.tasks.compose.SubscriptionNagBanner import org.tasks.data.CaldavDao import org.tasks.data.TagDataDao import org.tasks.data.TaskContainer @@ -186,17 +188,17 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL ?.show() is TaskListEvent.BegForSubscription -> { binding.banner.setContent { - val showBanner = rememberSaveable { mutableStateOf(true) } + var showBanner by rememberSaveable { mutableStateOf(true) } MdcTheme { - AnimatedBanner( - isVisible = showBanner, - dismiss = { - preferences.lastSubscribeRequest = now() - showBanner.value = false - }, + SubscriptionNagBanner( + visible = showBanner, subscribe = { + showBanner = false purchase() - showBanner.value = false + }, + dismiss = { + showBanner = false + preferences.lastSubscribeRequest = now() }, ) } diff --git a/app/src/main/java/org/tasks/compose/Banner.kt b/app/src/main/java/org/tasks/compose/Banner.kt index 6a6a06731..1d5a308b2 100644 --- a/app/src/main/java/org/tasks/compose/Banner.kt +++ b/app/src/main/java/org/tasks/compose/Banner.kt @@ -1,5 +1,6 @@ package org.tasks.compose +import android.content.res.Configuration import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.expandVertically @@ -16,51 +17,60 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import kotlinx.coroutines.delay +import com.google.android.material.composethemeadapter.MdcTheme import org.tasks.R -import org.tasks.Tasks.Companion.IS_GENERIC +import org.tasks.Tasks @ExperimentalAnimationApi @Composable fun AnimatedBanner( - isVisible: MutableState, - dismiss: () -> Unit, - subscribe: () -> Unit, + visible: Boolean, + content: @Composable () -> Unit, + buttons: @Composable () -> Unit, ) { - var show by rememberSaveable { mutableStateOf(false) } - if (isVisible.value) { - LaunchedEffect(key1 = isVisible, block = { - delay(500) - show = true - }) - } else { - show = false - } AnimatedVisibility( - visible = show, - enter = expandVertically( - expandFrom = Alignment.Top - ), + visible = visible, + enter = expandVertically(), exit = shrinkVertically(), ) { Column( modifier = Modifier .fillMaxWidth(), ) { + Divider() Spacer(modifier = Modifier.height(16.dp)) + content() + Row( + modifier = Modifier + .padding(vertical = 8.dp, horizontal = 16.dp) + .align(Alignment.End), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + buttons() + } + Divider() + } + } +} + +@ExperimentalAnimationApi +@Composable +fun SubscriptionNagBanner( + visible: Boolean, + subscribe: () -> Unit, + dismiss: () -> Unit, +) { + AnimatedBanner( + visible = visible, + content = { Text( text = stringResource( - id = if (IS_GENERIC) { + id = if (Tasks.IS_GENERIC) { R.string.enjoying_tasks } else { R.string.tasks_needs_your_support @@ -72,7 +82,7 @@ fun AnimatedBanner( Spacer(modifier = Modifier.height(4.dp)) Text( text = stringResource( - id = if (IS_GENERIC) { + id = if (Tasks.IS_GENERIC) { R.string.tasks_needs_your_support } else { R.string.support_development_subscribe @@ -81,28 +91,69 @@ fun AnimatedBanner( style = MaterialTheme.typography.body2, modifier = Modifier.padding(horizontal = 16.dp), ) - Row( - modifier = Modifier - .padding(vertical = 8.dp, horizontal = 16.dp) - .align(Alignment.End), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - TextButton(onClick = dismiss) { - Text(text = stringResource(id = R.string.dismiss)) - } - TextButton(onClick = subscribe) { - Text( - text = stringResource( - id = if (IS_GENERIC) { - R.string.TLA_menu_donate - } else { - R.string.button_subscribe - } - ) - ) + }, + buttons = { + TextButton(onClick = subscribe) { + Text(text = stringResource(id = R.string.dismiss)) + } + TextButton(onClick = dismiss) { + val res = if (Tasks.IS_GENERIC) { + R.string.TLA_menu_donate + } else { + R.string.button_subscribe } + Text(text = stringResource(id = res)) } - Divider() } - } -} \ No newline at end of file + ) +} + +@ExperimentalAnimationApi +@Composable +fun BeastModeBanner( + visible: Boolean, + showSettings: () -> Unit, + dismiss: () -> Unit, +) { + AnimatedBanner( + visible = visible, + content = { + Text( + text = stringResource(id = R.string.hint_customize_edit_title), + style = MaterialTheme.typography.body1, + modifier = Modifier.padding(horizontal = 16.dp), + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(id = R.string.hint_customize_edit_body), + style = MaterialTheme.typography.body2, + modifier = Modifier.padding(horizontal = 16.dp), + ) + }, + buttons = { + TextButton(onClick = dismiss) { + Text(text = stringResource(id = R.string.dismiss)) + } + TextButton(onClick = showSettings) { + Text(text = stringResource(id = R.string.TLA_menu_settings)) + } + } + ) +} + +@ExperimentalAnimationApi +@Preview(showBackground = true) +@Preview(showBackground = true, backgroundColor = 0x202124, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun BeastModePreview() = MdcTheme { + BeastModeBanner(visible = true, showSettings = {}, dismiss = {}) +} + +@ExperimentalAnimationApi +@Preview(showBackground = true) +@Preview(showBackground = true, backgroundColor = 0x202124, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun SubscriptionNagPreview() = MdcTheme { + SubscriptionNagBanner(visible = true, subscribe = {}, dismiss = {}) +} + diff --git a/app/src/main/java/org/tasks/preferences/Preferences.kt b/app/src/main/java/org/tasks/preferences/Preferences.kt index 106b41969..5ed4e95ab 100644 --- a/app/src/main/java/org/tasks/preferences/Preferences.kt +++ b/app/src/main/java/org/tasks/preferences/Preferences.kt @@ -540,6 +540,10 @@ class Preferences @JvmOverloads constructor( get() = getLong(R.string.p_last_subscribe_request, 0L) set(value) = setLong(R.string.p_last_subscribe_request, value) + var shownBeastModeHint: Boolean + get() = getBoolean(R.string.p_shown_beast_mode_hint, false) + set(value) = setBoolean(R.string.p_shown_beast_mode_hint, value) + companion object { private const val PREF_SORT_SORT = "sort_sort" // $NON-NLS-1$ diff --git a/app/src/main/res/layout/fragment_task_edit.xml b/app/src/main/res/layout/fragment_task_edit.xml index aca835118..5b8f23daf 100644 --- a/app/src/main/res/layout/fragment_task_edit.xml +++ b/app/src/main/res/layout/fragment_task_edit.xml @@ -66,6 +66,11 @@ android:orientation="vertical" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> + + app_bar_collapse completed_tasks_at_bottom completed_tasks_sort + shown_beast_mode_hint diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ccd06d7b8..eb374d6bb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -735,4 +735,6 @@ File %1$s contained %2$s.\n\n Server type Snoozed Dismiss + Too much information? + You can customize this screen by rearranging or removing fields