Move subscription begging to viewmodel

pull/2536/head
Alex Baker 2 years ago
parent ea8a4b5e2d
commit db66a66578

@ -57,8 +57,6 @@ import org.tasks.ui.MainActivityEvent
import org.tasks.ui.MainActivityEventBus import org.tasks.ui.MainActivityEventBus
import org.tasks.ui.NavigationDrawerFragment import org.tasks.ui.NavigationDrawerFragment
import org.tasks.ui.NavigationDrawerFragment.Companion.newNavigationDrawer import org.tasks.ui.NavigationDrawerFragment.Companion.newNavigationDrawer
import org.tasks.ui.TaskListEvent
import org.tasks.ui.TaskListEventBus
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -76,7 +74,6 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler, Timer
@Inject lateinit var tagDataDao: TagDataDao @Inject lateinit var tagDataDao: TagDataDao
@Inject lateinit var alarmDao: AlarmDao @Inject lateinit var alarmDao: AlarmDao
@Inject lateinit var eventBus: MainActivityEventBus @Inject lateinit var eventBus: MainActivityEventBus
@Inject lateinit var taskListEventBus: TaskListEventBus
@Inject lateinit var playServices: PlayServices @Inject lateinit var playServices: PlayServices
@Inject lateinit var firebase: Firebase @Inject lateinit var firebase: Firebase
@ -476,16 +473,6 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler, Timer
actionMode = null actionMode = null
} }
override fun onStart() {
super.onStart()
lifecycleScope.launch {
if (!inventory.hasPro && !firebase.subscribeCooldown) {
taskListEventBus.tryEmit(TaskListEvent.BegForSubscription)
}
}
}
companion object { companion object {
/** For indicating the new list screen should be launched at fragment setup time */ /** For indicating the new list screen should be launched at fragment setup time */
const val TOKEN_CREATE_NEW_LIST_NAME = "newListName" // $NON-NLS-1$ const val TOKEN_CREATE_NEW_LIST_NAME = "newListName" // $NON-NLS-1$

@ -24,10 +24,6 @@ import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.compose.animation.ExperimentalAnimationApi 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.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.app.ShareCompat import androidx.core.app.ShareCompat
import androidx.core.view.forEach import androidx.core.view.forEach
@ -49,7 +45,6 @@ import com.google.android.material.composethemeadapter.MdcTheme
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.todoroo.andlib.utility.AndroidUtilities import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.andlib.utility.DateUtilities import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.andlib.utility.DateUtilities.now
import com.todoroo.astrid.adapter.TaskAdapter import com.todoroo.astrid.adapter.TaskAdapter
import com.todoroo.astrid.adapter.TaskAdapterProvider import com.todoroo.astrid.adapter.TaskAdapterProvider
import com.todoroo.astrid.api.AstridApiConstants.EXTRAS_OLD_DUE_DATE import com.todoroo.astrid.api.AstridApiConstants.EXTRAS_OLD_DUE_DATE
@ -82,15 +77,14 @@ import kotlinx.coroutines.withContext
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
import org.tasks.R import org.tasks.R
import org.tasks.ShortcutManager import org.tasks.ShortcutManager
import org.tasks.Tasks.Companion.IS_GOOGLE_PLAY
import org.tasks.activities.FilterSettingsActivity import org.tasks.activities.FilterSettingsActivity
import org.tasks.activities.GoogleTaskListSettingsActivity import org.tasks.activities.GoogleTaskListSettingsActivity
import org.tasks.activities.PlaceSettingsActivity import org.tasks.activities.PlaceSettingsActivity
import org.tasks.activities.TagSettingsActivity import org.tasks.activities.TagSettingsActivity
import org.tasks.analytics.Firebase import org.tasks.analytics.Firebase
import org.tasks.billing.PurchaseActivity
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
import org.tasks.compose.SubscriptionNagBanner import org.tasks.compose.SubscriptionNagBanner
import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.data.CaldavDao import org.tasks.data.CaldavDao
import org.tasks.data.TagDataDao import org.tasks.data.TagDataDao
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
@ -179,7 +173,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
private lateinit var callbacks: TaskListFragmentCallbackHandler private lateinit var callbacks: TaskListFragmentCallbackHandler
private lateinit var binding: FragmentTaskListBinding private lateinit var binding: FragmentTaskListBinding
@OptIn(ExperimentalAnimationApi::class)
private fun process(event: TaskListEvent) = when (event) { private fun process(event: TaskListEvent) = when (event) {
is TaskListEvent.TaskCreated -> is TaskListEvent.TaskCreated ->
onTaskCreated(event.uuid) onTaskCreated(event.uuid)
@ -187,36 +180,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
makeSnackbar(R.string.calendar_event_created, event.title) makeSnackbar(R.string.calendar_event_created, event.title)
?.setAction(R.string.action_open) { context?.openUri(event.uri) } ?.setAction(R.string.action_open) { context?.openUri(event.uri) }
?.show() ?.show()
is TaskListEvent.BegForSubscription -> {
binding.banner.setContent {
var showBanner by rememberSaveable { mutableStateOf(true) }
MdcTheme {
SubscriptionNagBanner(
visible = showBanner,
subscribe = {
showBanner = false
preferences.lastSubscribeRequest = now()
purchase()
firebase.logEvent(R.string.event_banner_sub, R.string.param_click to true)
},
dismiss = {
showBanner = false
preferences.lastSubscribeRequest = now()
firebase.logEvent(R.string.event_banner_sub, R.string.param_click to false)
},
)
}
}
}
}
private fun purchase() {
if (IS_GOOGLE_PLAY) {
startActivity(Intent(context, PurchaseActivity::class.java))
} else {
preferences.lastSubscribeRequest = now()
context?.openUri(R.string.url_donate)
}
} }
override fun onRefresh() { override fun onRefresh() {
@ -266,6 +229,7 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
.launchIn(viewLifecycleOwner.lifecycleScope) .launchIn(viewLifecycleOwner.lifecycleScope)
} }
@OptIn(ExperimentalAnimationApi::class)
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentTaskListBinding.inflate(inflater, container, false) binding = FragmentTaskListBinding.inflate(inflater, container, false)
@ -292,9 +256,9 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
recyclerView.layoutManager = LinearLayoutManager(context) recyclerView.layoutManager = LinearLayoutManager(context)
lifecycleScope.launch { lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
listViewModel.tasks.collect { listViewModel.state.collect {
submitList(it) submitList(it.tasks)
if (it.isEmpty()) { if (it.tasks.isEmpty()) {
swipeRefreshLayout.visibility = View.GONE swipeRefreshLayout.visibility = View.GONE
emptyRefreshLayout.visibility = View.VISIBLE emptyRefreshLayout.visibility = View.VISIBLE
} else { } else {
@ -343,6 +307,16 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
} }
finishActionMode() finishActionMode()
} }
binding.banner.setContent {
val showBanner = listViewModel.state.collectAsStateLifecycleAware().value.begForSubscription
MdcTheme {
SubscriptionNagBanner(
visible = showBanner,
subscribe = { listViewModel.dismissBanner(clickedPurchase = true) },
dismiss = { listViewModel.dismissBanner(clickedPurchase = false) },
)
}
}
return binding.root return binding.root
} }

@ -58,7 +58,7 @@ class SubtaskControlSet : TaskEditControlFragment() {
filter = viewModel.selectedList.collectAsStateLifecycleAware().value, filter = viewModel.selectedList.collectAsStateLifecycleAware().value,
hasParent = viewModel.hasParent, hasParent = viewModel.hasParent,
desaturate = preferences.desaturateDarkMode, desaturate = preferences.desaturateDarkMode,
existingSubtasks = listViewModel.tasks.collectAsStateLifecycleAware(initial = emptyList()).value, existingSubtasks = listViewModel.state.collectAsStateLifecycleAware().value.tasks,
newSubtasks = viewModel.newSubtasks.collectAsStateLifecycleAware().value, newSubtasks = viewModel.newSubtasks.collectAsStateLifecycleAware().value,
openSubtask = this@SubtaskControlSet::openSubtask, openSubtask = this@SubtaskControlSet::openSubtask,
completeExistingSubtask = this@SubtaskControlSet::complete, completeExistingSubtask = this@SubtaskControlSet::complete,

@ -7,5 +7,4 @@ typealias TaskListEventBus = MutableSharedFlow<TaskListEvent>
sealed interface TaskListEvent { sealed interface TaskListEvent {
data class TaskCreated(val uuid: String) : TaskListEvent data class TaskCreated(val uuid: String) : TaskListEvent
data class CalendarEventCreated(val title: String?, val uri: String) : TaskListEvent data class CalendarEventCreated(val title: String?, val uri: String) : TaskListEvent
object BegForSubscription : TaskListEvent
} }

@ -1,44 +1,59 @@
package org.tasks.ui package org.tasks.ui
import android.annotation.SuppressLint
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.todoroo.andlib.utility.DateUtilities import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.Tasks
import org.tasks.analytics.Firebase
import org.tasks.billing.Inventory
import org.tasks.billing.PurchaseActivity
import org.tasks.compose.throttleLatest import org.tasks.compose.throttleLatest
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
import org.tasks.data.TaskDao import org.tasks.data.TaskDao
import org.tasks.data.TaskListQuery.getQuery import org.tasks.data.TaskListQuery.getQuery
import org.tasks.extensions.Context.openUri
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@SuppressLint("StaticFieldLeak")
class TaskListViewModel @Inject constructor( class TaskListViewModel @Inject constructor(
private val preferences: Preferences, @ApplicationContext private val context: Context,
private val taskDao: TaskDao, private val preferences: Preferences,
private val localBroadcastManager: LocalBroadcastManager, private val taskDao: TaskDao,
private val localBroadcastManager: LocalBroadcastManager,
private val inventory: Inventory,
private val firebase: Firebase,
) : ViewModel() { ) : ViewModel() {
data class State( data class State(
val filter: Filter? = null, val filter: Filter? = null,
val now: Long = DateUtilities.now(), val now: Long = DateUtilities.now(),
val tasks: List<TaskContainer> = emptyList(),
val begForSubscription: Boolean = false,
) )
private val _state = MutableStateFlow(State()) private val _state = MutableStateFlow(State())
val state = _state.asStateFlow()
val tasks: Flow<List<TaskContainer>> =
_state
.filter { it.filter != null }
.throttleLatest(333)
.map { taskDao.fetchTasks { getQuery(preferences, it.filter!!) } }
private val refreshReceiver = object : BroadcastReceiver() { private val refreshReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
@ -56,8 +71,44 @@ class TaskListViewModel @Inject constructor(
_state.update { it.copy(now = DateUtilities.now()) } _state.update { it.copy(now = DateUtilities.now()) }
} }
fun dismissBanner(clickedPurchase: Boolean) {
_state.update {
it.copy(begForSubscription = false)
}
preferences.lastSubscribeRequest = DateUtilities.now()
firebase.logEvent(R.string.event_banner_sub, R.string.param_click to clickedPurchase)
if (clickedPurchase) {
if (Tasks.IS_GOOGLE_PLAY) {
context.startActivity(Intent(context, PurchaseActivity::class.java))
} else {
preferences.lastSubscribeRequest = DateUtilities.now()
context.openUri(R.string.url_donate)
}
}
}
init { init {
localBroadcastManager.registerRefreshReceiver(refreshReceiver) localBroadcastManager.registerRefreshReceiver(refreshReceiver)
_state
.filter { it.filter != null }
.throttleLatest(333)
.map { taskDao.fetchTasks { getQuery(preferences, it.filter!!) } }
.onEach { tasks ->
_state.update {
it.copy(tasks = tasks)
}
}
.flowOn(Dispatchers.Default)
.launchIn(viewModelScope)
viewModelScope.launch(Dispatchers.Default) {
if (!inventory.hasPro && !firebase.subscribeCooldown) {
_state.update {
it.copy(begForSubscription = true)
}
}
}
} }
override fun onCleared() { override fun onCleared() {

Loading…
Cancel
Save