Skip to content
This repository has been archived by the owner on Nov 5, 2024. It is now read-only.

Commit

Permalink
Disclaimer screen + bugfixes (#3098)
Browse files Browse the repository at this point in the history
* WIP: Disclaimer screen

* Build UI components

* WIP: Disclaimer screen

* Implement the Disclaimer screen

* Fix crash

* Finish the Disclaimer screen

* Make `stale` keep bugs

* WIP: Fix memoization

* Fix memoization issues

* Bugfixes: memoization + Categories screen

* Optimize memoization

* Fix tags memoization

* Get rid of legacy `EventBus`

* Bump version to "4.6.2" (162)

* Add "Report a bug" btn in Settings
  • Loading branch information
ILIYANGERMANOV authored Apr 6, 2024
1 parent 4d55e4d commit b63f146
Show file tree
Hide file tree
Showing 65 changed files with 972 additions and 423 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ jobs:
days-before-issue-close: 7
days-before-pr-stale: 2
days-before-pr-close: 1
exempt-issue-labels: keep,P0
exempt-issue-labels: keep,P0,bug
exempt-pr-labels: keep,P0
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ dependencies {
implementation(projects.screen.budgets)
implementation(projects.screen.categories)
implementation(projects.screen.contributors)
implementation(projects.screen.disclaimer)
implementation(projects.screen.editTransaction)
implementation(projects.screen.exchangeRates)
implementation(projects.screen.features)
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/com/ivy/IvyNavGraph.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.ivy.balance.BalanceScreen
import com.ivy.budgets.BudgetScreen
import com.ivy.categories.CategoriesScreen
import com.ivy.contributors.ContributorsScreenImpl
import com.ivy.disclaimer.DisclaimerScreenImpl
import com.ivy.exchangerates.ExchangeRatesScreen
import com.ivy.features.FeaturesScreenImpl
import com.ivy.importdata.csv.CSVScreen
Expand All @@ -22,6 +23,7 @@ import com.ivy.navigation.BudgetScreen
import com.ivy.navigation.CSVScreen
import com.ivy.navigation.CategoriesScreen
import com.ivy.navigation.ContributorsScreen
import com.ivy.navigation.DisclaimerScreen
import com.ivy.navigation.EditPlannedScreen
import com.ivy.navigation.EditTransactionScreen
import com.ivy.navigation.ExchangeRatesScreen
Expand Down Expand Up @@ -81,5 +83,6 @@ fun BoxWithConstraintsScope.IvyNavGraph(screen: Screen?) {
AttributionsScreen -> AttributionsScreenImpl()
ContributorsScreen -> ContributorsScreenImpl()
ReleasesScreen -> ReleasesScreenImpl()
DisclaimerScreen -> DisclaimerScreenImpl()
}
}
13 changes: 6 additions & 7 deletions app/src/main/java/com/ivy/wallet/RootViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import com.ivy.base.legacy.SharedPrefs
import com.ivy.base.legacy.Theme
import com.ivy.base.legacy.stringRes
import com.ivy.base.model.TransactionType
import com.ivy.data.InMemoryDataStore
import com.ivy.data.db.dao.read.SettingsDao
import com.ivy.data.repository.LegalRepository
import com.ivy.frp.test.TestIdlingResource
import com.ivy.legacy.IvyWalletCtx
import com.ivy.legacy.utils.ioThread
import com.ivy.legacy.utils.readOnly
import com.ivy.navigation.DisclaimerScreen
import com.ivy.navigation.EditTransactionScreen
import com.ivy.navigation.MainScreen
import com.ivy.navigation.Navigation
Expand All @@ -40,7 +41,7 @@ class RootViewModel @Inject constructor(
private val sharedPrefs: SharedPrefs,
private val transactionReminderLogic: TransactionReminderLogic,
private val migrationsManager: MigrationsManager,
private val inMemoryDataStore: InMemoryDataStore,
private val legalRepo: LegalRepository,
) : ViewModel() {

companion object {
Expand All @@ -54,11 +55,6 @@ class RootViewModel @Inject constructor(
private val _appLocked = MutableStateFlow<Boolean?>(null)
val appLocked = _appLocked.readOnly()

init {
// TODO: Consider delaying this to improve cold start
inMemoryDataStore.init(viewModelScope)
}

fun start(systemDarkMode: Boolean, intent: Intent) {
viewModelScope.launch {
TestIdlingResource.increment()
Expand Down Expand Up @@ -87,6 +83,9 @@ class RootViewModel @Inject constructor(
} else {
nav.navigateTo(OnboardingScreen)
}
if (!legalRepo.isDisclaimerAccepted()) {
nav.navigateTo(DisclaimerScreen)
}
}

TestIdlingResource.decrement()
Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ paparazzi = "1.3.3"
# Android
min-sdk = "28"
compile-sdk = "34"
version-name = "4.6.1"
version-code = "161"
version-name = "4.6.2"
version-code = "162"
jvm-target = "17"


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.viewModelScope
import com.ivy.ui.ComposeViewModel
import com.ivy.domain.event.AccountUpdatedEvent
import com.ivy.domain.event.EventBus
import com.ivy.base.legacy.SharedPrefs
import com.ivy.data.DataObserver
import com.ivy.data.DataWriteEvent
import com.ivy.data.repository.AccountRepository
import com.ivy.legacy.IvyWalletCtx
import com.ivy.legacy.data.model.AccountData
import com.ivy.legacy.data.model.toCloseTimeRange
import com.ivy.legacy.utils.format
import com.ivy.legacy.utils.ioThread
import com.ivy.ui.ComposeViewModel
import com.ivy.ui.R
import com.ivy.wallet.domain.action.settings.BaseCurrencyAct
import com.ivy.wallet.domain.action.viewmodel.account.AccountDataAct
Expand All @@ -26,6 +26,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import javax.inject.Inject

Expand All @@ -40,8 +41,8 @@ class AccountsViewModel @Inject constructor(
private val calcWalletBalanceAct: CalcWalletBalanceAct,
private val baseCurrencyAct: BaseCurrencyAct,
private val accountDataAct: AccountDataAct,
private val eventBus: EventBus,
private val accountRepository: AccountRepository
private val accountRepository: AccountRepository,
private val dataObserver: DataObserver,
) : ComposeViewModel<AccountsState, AccountsEvent>() {
private val baseCurrency = mutableStateOf("")
private val accountsData = mutableStateOf(listOf<AccountData>())
Expand All @@ -53,8 +54,16 @@ class AccountsViewModel @Inject constructor(

init {
viewModelScope.launch {
eventBus.subscribe(AccountUpdatedEvent) {
onStart()
dataObserver.writeEvents.collectLatest { event ->
when (event) {
is DataWriteEvent.AccountChange -> {
onStart()
}

else -> {
// do nothing
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import com.ivy.ui.ComposeViewModel
import com.ivy.data.repository.CategoryRepository
import com.ivy.frp.action.thenMap
import com.ivy.frp.thenInvokeAfter
import com.ivy.legacy.data.model.TimePeriod
import com.ivy.legacy.datamodel.Account
import com.ivy.legacy.utils.ioThread
import com.ivy.wallet.domain.action.account.AccountsAct
import com.ivy.wallet.domain.action.category.LegacyCategoryIncomeWithAccountFiltersAct
import com.ivy.wallet.domain.action.settings.BaseCurrencyAct
Expand Down Expand Up @@ -109,8 +111,8 @@ class CategoriesViewModel @Inject constructor(
}

private suspend fun initialise() {
com.ivy.legacy.utils.ioThread {
val range = com.ivy.legacy.data.model.TimePeriod.currentMonth(
ioThread {
val range = TimePeriod.currentMonth(
startDayOfMonth = ivyContext.startDayOfMonth
).toRange(ivyContext.startDayOfMonth) // this must be monthly

Expand Down Expand Up @@ -169,14 +171,14 @@ class CategoriesViewModel @Inject constructor(
val sortedList = sortList(newOrder, sortOrder).toImmutableList()

if (sortOrder == SortOrder.DEFAULT) {
com.ivy.legacy.utils.ioThread {
ioThread {
sortedList.forEachIndexed { index, categoryData ->
categoryRepository.save(categoryData.category)
categoryRepository.save(categoryData.category.copy(orderNum = index.toDouble()))
}
}
}

com.ivy.legacy.utils.ioThread {
ioThread {
sharedPrefs.putInt(SharedPrefs.CATEGORY_SORT_ORDER, sortOrder.orderNum)
}

Expand Down
15 changes: 15 additions & 0 deletions screen/disclaimer/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
plugins {
id("ivy.feature")
}

android {
namespace = "com.ivy.disclaimer"
}

dependencies {
implementation(projects.shared.data.core)
implementation(projects.shared.ui.core)
implementation(projects.shared.ui.navigation)

testImplementation(projects.shared.ui.testing)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.ivy.disclaimer

import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.ivy.disclaimer.composables.AcceptTermsText
import com.ivy.disclaimer.composables.AgreeButton
import com.ivy.disclaimer.composables.AgreementCheckBox
import com.ivy.disclaimer.composables.DisclaimerTopAppBar
import com.ivy.navigation.screenScopedViewModel
import com.ivy.ui.component.OpenSourceCard

@Composable
fun DisclaimerScreenImpl() {
val viewModel: DisclaimerViewModel = screenScopedViewModel()
val viewState = viewModel.uiState()
DisclaimerScreenUi(viewState = viewState, onEvent = viewModel::onEvent)
}

@Composable
fun DisclaimerScreenUi(
viewState: DisclaimerViewState,
onEvent: (DisclaimerViewEvent) -> Unit,
modifier: Modifier = Modifier,
) {
Scaffold(
modifier = modifier,
topBar = {
DisclaimerTopAppBar()
},
content = { innerPadding ->
Content(
modifier = Modifier.padding(innerPadding),
viewState = viewState,
onEvent = onEvent,
)
}
)
}

@Composable
private fun Content(
viewState: DisclaimerViewState,
onEvent: (DisclaimerViewEvent) -> Unit,
modifier: Modifier = Modifier
) {
LazyColumn(
modifier = modifier.padding(horizontal = 16.dp)
) {
item {
OpenSourceCard()
}
item {
Spacer(modifier = Modifier.height(12.dp))
AcceptTermsText()
}
itemsIndexed(items = viewState.checkboxes) { index, item ->
Spacer(modifier = Modifier.height(8.dp))
AgreementCheckBox(
viewState = item,
onClick = {
onEvent(DisclaimerViewEvent.OnCheckboxClick(index))
}
)
}
item {
Spacer(modifier = Modifier.height(12.dp))
AgreeButton(
enabled = viewState.agreeButtonEnabled,
) { onEvent(DisclaimerViewEvent.OnAgreeClick) }
}
item {
// To ensure proper scrolling
Spacer(modifier = Modifier.height(48.dp))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.ivy.disclaimer

import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.viewModelScope
import com.ivy.data.repository.LegalRepository
import com.ivy.navigation.Navigation
import com.ivy.ui.ComposeViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class DisclaimerViewModel @Inject constructor(
private val navigation: Navigation,
private val legalRepo: LegalRepository,
) : ComposeViewModel<DisclaimerViewState, DisclaimerViewEvent>() {

private var checkboxes by mutableStateOf(LegalCheckboxes)

@Composable
override fun uiState(): DisclaimerViewState {
return DisclaimerViewState(
checkboxes = checkboxes,
agreeButtonEnabled = checkboxes.all(CheckboxViewState::checked),
)
}

override fun onEvent(event: DisclaimerViewEvent) {
when (event) {
DisclaimerViewEvent.OnAgreeClick -> handleAgreeClick()
is DisclaimerViewEvent.OnCheckboxClick -> handleCheckboxClick(event)
}
}

private fun handleAgreeClick() {
viewModelScope.launch {
legalRepo.setDisclaimerAccepted(accepted = true)
navigation.back()
}
}

private fun handleCheckboxClick(event: DisclaimerViewEvent.OnCheckboxClick) {
checkboxes = checkboxes.mapIndexed { index, item ->
if (index == event.index) {
item.copy(
checked = !item.checked
)
} else {
item
}
}.toImmutableList()
}

companion object {
// LEGAL text - do NOT extract or translate
val LegalCheckboxes = listOf(
CheckboxViewState(
text = "I recognize this app is open-source and provided 'as-is' " +
"with no warranties, explicit or implied. " +
"I fully accept all risks of errors, defects, or failures, " +
"using the app solely at my own risk.",
checked = false,
),
CheckboxViewState(
text = "I understand there is no warranty for the accuracy, " +
"reliability, or completeness of my data. " +
"Manual data backup is my responsibility, and I agree to not hold " +
"the app liable for any data loss.",
checked = false,
),
CheckboxViewState(
text = "I hereby release the app developers, contributors, " +
"and distributing company from any liability for " +
"claims, damages, legal fees, or losses, including those resulting " +
"from security breaches or data inaccuracies.",
checked = false,
),
CheckboxViewState(
text = "I am aware and accept that the app may display misleading information " +
"or contain inaccuracies. " +
"I assume full responsibility for verifying the integrity " +
"of financial data and calculations before making " +
"any decisions based on app data.",
checked = false,
),
).toImmutableList()
}
}
Loading

0 comments on commit b63f146

Please sign in to comment.