Skip to content

Commit

Permalink
Merge pull request #140 from ILIYANGERMANOV/bugfixes-and-improvements
Browse files Browse the repository at this point in the history
Bugfixes and improvements
  • Loading branch information
ILIYANGERMANOV committed Nov 16, 2021
2 parents 0c25f38 + 66a2381 commit 906b8e1
Show file tree
Hide file tree
Showing 19 changed files with 452 additions and 37 deletions.
18 changes: 10 additions & 8 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,22 @@
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />

<!-- Fix Lint WorkManger build error -->
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:exported="false"
tools:node="remove" />

<receiver android:name=".widget.AddTransactionWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/transaction_widget_info" />
android:resource="@xml/add_transaction_widget_info" />
</receiver>

<receiver android:name=".widget.AddTransactionWidgetCompact">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/add_transaction_widget_compact_info" />
</receiver>
</application>

Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/com/ivy/wallet/base/UtilExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ fun String.uppercaseLocal(): String = this.toUpperCase(Locale.getDefault())

fun String.capitalizeLocal(): String = this.capitalize(Locale.getDefault())

fun String.capitalizeWords(): String {
return split(" ").joinToString(" ") { it.capitalizeLocal() }
}

fun hasLockScreen(context: Context): Boolean {
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
return keyguardManager.isDeviceSecure
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/java/com/ivy/wallet/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -615,4 +615,13 @@ object AppModule {
ivyContext = ivyContext
)
}

@Provides
fun provideSmartTitleSuggestionsLogic(
transactionDao: TransactionDao
): SmartTitleSuggestionsLogic {
return SmartTitleSuggestionsLogic(
transactionDao = transactionDao
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class CustomerJourneyLogic(
backgroundColor = GreenLight,
hasDismiss = true,
onAction = { _, ivyActivity ->
ivyActivity.pinAddTransactionWidget()
ivyActivity.pinAddTransactionWidgetCompact()
}
)

Expand Down
111 changes: 111 additions & 0 deletions app/src/main/java/com/ivy/wallet/logic/SmartTitleSuggestionsLogic.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.ivy.wallet.logic

import com.ivy.wallet.base.capitalizeWords
import com.ivy.wallet.base.isNotNullOrBlank
import com.ivy.wallet.model.entity.Transaction
import com.ivy.wallet.persistence.dao.TransactionDao
import com.ivy.wallet.ui.edit.core.SUGGESTIONS_LIMIT
import java.util.*

class SmartTitleSuggestionsLogic(
private val transactionDao: TransactionDao
) {

/**
* Suggests titles based on:
* - title match
* - most used titles for categories
* - if suggestions.size < SUGGESTIONS_LIMIT most used titles for accounts
*/
fun suggest(
title: String?,
categoryId: UUID?,
accountId: UUID?
): Set<String> {
val suggestions = mutableSetOf<String>()

if (title != null && title.isNotEmpty()) {
//suggest by title
val suggestionsByTitle = transactionDao.findAllByTitleMatchingPattern("${title}%")
.extractUniqueTitles()
.sortedByMostUsedFirst {
transactionDao.countByTitleMatchingPattern("${it}%")
}

suggestions.addAll(suggestionsByTitle)
}

if (categoryId != null) {
//suggest by category
//all titles used for the specific category
//ordered by N times used

val suggestionsByCategory = transactionDao
.findAllByCategory(
categoryId = categoryId
)
//exclude already suggested suggestions so they're ordered by priority at the end
.extractUniqueTitles(excludeSuggestions = suggestions)
.sortedByMostUsedFirst {
transactionDao.countByTitleMatchingPatternAndCategoryId(
pattern = it,
categoryId = categoryId
)
}

suggestions.addAll(suggestionsByCategory)
}


if (suggestions.size < SUGGESTIONS_LIMIT && accountId != null) {
//last resort, suggest by account
//all titles used for the specific account
//ordered by N times used

val suggestionsByAccount = transactionDao
.findAllByAccount(
accountId = accountId
)
//exclude already suggested suggestions so they're ordered by priority at the end
.extractUniqueTitles(excludeSuggestions = suggestions)
.sortedByMostUsedFirst {
transactionDao.countByTitleMatchingPatternAndAccountId(
pattern = it,
accountId = accountId
)
}

suggestions.addAll(suggestionsByAccount)
}

return suggestions
.filter { it != title }
.toSet()
}
}

private fun List<Transaction>.extractUniqueTitles(
excludeSuggestions: Set<String>? = null
): Set<String> {
return this
.filter { it.title.isNotNullOrBlank() }
.map { it.title!!.trim().capitalizeWords() }
.filter { excludeSuggestions == null || !excludeSuggestions.contains(it) }
.toSet()
}

private fun Set<String>.sortedByMostUsedFirst(countUses: (String) -> Long): Set<String> {
val titleCountMap = this
.map {
it to countUses(it)
}
.toMap()

val sortedSuggestions = this
.sortedByDescending {
titleCountMap.getOrDefault(it, 0)
}
.toSet()

return sortedSuggestions
}
31 changes: 31 additions & 0 deletions app/src/main/java/com/ivy/wallet/persistence/dao/TransactionDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,35 @@ interface TransactionDao {

@Query("SELECT COUNT(*) FROM transactions WHERE isDeleted = 0 AND dateTime IS NOT null")
fun countHappenedTransactions(): Long

//Smart Title Suggestions
@Query("SELECT * FROM transactions WHERE title LIKE :pattern AND isDeleted = 0")
fun findAllByTitleMatchingPattern(pattern: String): List<Transaction>

@Query("SELECT COUNT(*) FROM transactions WHERE title LIKE :pattern AND isDeleted = 0")
fun countByTitleMatchingPattern(
pattern: String,
): Long

@Query("SELECT * FROM transactions WHERE isDeleted = 0 AND (categoryId = :categoryId OR seAutoCategoryId = :categoryId) ORDER BY dateTime DESC")
fun findAllByCategory(
categoryId: UUID,
): List<Transaction>

@Query("SELECT COUNT(*) FROM transactions WHERE title LIKE :pattern AND categoryId = :categoryId AND isDeleted = 0")
fun countByTitleMatchingPatternAndCategoryId(
pattern: String,
categoryId: UUID
): Long

@Query("SELECT * FROM transactions WHERE isDeleted = 0 AND accountId = :accountId ORDER BY dateTime DESC")
fun findAllByAccount(
accountId: UUID
): List<Transaction>

@Query("SELECT COUNT(*) FROM transactions WHERE title LIKE :pattern AND accountId = :accountId AND isDeleted = 0")
fun countByTitleMatchingPatternAndAccountId(
pattern: String,
accountId: UUID
): Long
}
11 changes: 10 additions & 1 deletion app/src/main/java/com/ivy/wallet/ui/IvyActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import com.ivy.wallet.ui.test.TestScreen
import com.ivy.wallet.ui.theme.*
import com.ivy.wallet.ui.webView.WebViewScreen
import com.ivy.wallet.widget.AddTransactionWidget
import com.ivy.wallet.widget.AddTransactionWidgetCompact
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
import java.time.LocalDate
Expand Down Expand Up @@ -492,8 +493,16 @@ class IvyActivity : AppCompatActivity() {
}

fun pinAddTransactionWidget() {
pinWidget(AddTransactionWidget::class.java)
}

fun pinAddTransactionWidgetCompact() {
pinWidget(AddTransactionWidgetCompact::class.java)
}

private fun <T> pinWidget(widget: Class<T>) {
val appWidgetManager: AppWidgetManager = this.getSystemService(AppWidgetManager::class.java)
val addTransactionWidget = ComponentName(this, AddTransactionWidget::class.java)
val addTransactionWidget = ComponentName(this, widget)
appWidgetManager.requestPinAppWidget(addTransactionWidget, null, null)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ fun BoxWithConstraintsScope.EditTransactionScreen(screen: Screen.EditTransaction

val transactionType by viewModel.transactionType.observeAsState(screen.type)
val initialTitle by viewModel.initialTitle.observeAsState()
val titleSuggestions by viewModel.titleSuggestions.collectAsState()
val currency by viewModel.currency.observeAsState("")
val description by viewModel.description.observeAsState()
val dateTime by viewModel.dateTime.observeAsState()
Expand All @@ -65,6 +66,7 @@ fun BoxWithConstraintsScope.EditTransactionScreen(screen: Screen.EditTransaction
transactionType = transactionType,
baseCurrency = currency,
initialTitle = initialTitle,
titleSuggestions = titleSuggestions,
description = description,
dateTime = dateTime,
category = category,
Expand Down Expand Up @@ -105,6 +107,7 @@ private fun BoxWithConstraintsScope.UI(
transactionType: TransactionType,
baseCurrency: String,
initialTitle: String?,
titleSuggestions: Set<String>,
description: String?,
category: Category?,
dateTime: LocalDateTime?,
Expand Down Expand Up @@ -178,11 +181,13 @@ private fun BoxWithConstraintsScope.UI(
Title(
type = transactionType,
titleFocus = titleFocus,
initialTransactionId = screen.initialTransactionId,

titleTextFieldValue = titleTextFieldValue,
setTitleTextFieldValue = {
titleTextFieldValue = it
},
suggestions = titleSuggestions,

onTitleChanged = onTitleChanged,
onNext = {
Expand Down Expand Up @@ -429,6 +434,7 @@ private fun Preview() {
UI(
screen = Screen.EditTransaction(null, TransactionType.EXPENSE),
initialTitle = "",
titleSuggestions = emptySet(),
baseCurrency = "BGN",
dateTime = timeNowLocal(),
description = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ import com.ivy.wallet.base.asLiveData
import com.ivy.wallet.base.ioThread
import com.ivy.wallet.base.timeNowUTC
import com.ivy.wallet.event.AccountsUpdatedEvent
import com.ivy.wallet.logic.AccountCreator
import com.ivy.wallet.logic.CategoryCreator
import com.ivy.wallet.logic.PaywallLogic
import com.ivy.wallet.logic.PlannedPaymentsLogic
import com.ivy.wallet.logic.*
import com.ivy.wallet.logic.currency.ExchangeRatesLogic
import com.ivy.wallet.logic.model.CreateAccountData
import com.ivy.wallet.logic.model.CreateCategoryData
Expand All @@ -24,6 +21,8 @@ import com.ivy.wallet.sync.uploader.TransactionUploader
import com.ivy.wallet.ui.IvyContext
import com.ivy.wallet.ui.Screen
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import java.time.LocalDateTime
Expand All @@ -43,7 +42,8 @@ class EditTransactionViewModel @Inject constructor(
private val categoryCreator: CategoryCreator,
private val accountCreator: AccountCreator,
private val paywallLogic: PaywallLogic,
private val plannedPaymentsLogic: PlannedPaymentsLogic
private val plannedPaymentsLogic: PlannedPaymentsLogic,
private val smartTitleSuggestionsLogic: SmartTitleSuggestionsLogic
) : ViewModel() {

private val _transactionType = MutableLiveData<TransactionType>()
Expand All @@ -52,6 +52,9 @@ class EditTransactionViewModel @Inject constructor(
private val _initialTitle = MutableLiveData<String?>()
val initialTitle = _initialTitle.asLiveData()

private val _titleSuggestions = MutableStateFlow(emptySet<String>())
val titleSuggestions = _titleSuggestions.asStateFlow()

private val _currency = MutableLiveData<String>()
val currency = _currency.asLiveData()

Expand Down Expand Up @@ -182,6 +185,22 @@ class EditTransactionViewModel @Inject constructor(
this.title = newTitle

saveIfEditMode()

updateTitleSuggestions(newTitle)
}

private fun updateTitleSuggestions(title: String? = loadedTransaction().title) {
if (editMode) return //title suggestions should not be display for Edit mode

viewModelScope.launch {
_titleSuggestions.value = ioThread {
smartTitleSuggestionsLogic.suggest(
title = title,
categoryId = category.value?.id,
accountId = account.value?.id
)
}
}
}

fun onDescriptionChanged(newDescription: String?) {
Expand All @@ -200,6 +219,8 @@ class EditTransactionViewModel @Inject constructor(
_category.value = newCategory

saveIfEditMode()

updateTitleSuggestions()
}

fun onAccountChanged(newAccount: Account) {
Expand All @@ -216,6 +237,8 @@ class EditTransactionViewModel @Inject constructor(
sharedPrefs.putString(SharedPrefs.LAST_SELECTED_ACCOUNT_ID, newAccount.id.toString())

saveIfEditMode()

updateTitleSuggestions()
}

fun onToAccountChanged(newAccount: Account) {
Expand Down
Loading

0 comments on commit 906b8e1

Please sign in to comment.