From ed602f2bace3804e39a409996222fa1fd5a80a77 Mon Sep 17 00:00:00 2001 From: Iliyan Germanov Date: Sat, 15 Apr 2023 21:05:05 +0300 Subject: [PATCH] WIP: Manual CSV mapping --- .../java/com/ivy/wallet/ui/csv/CSVEvent.kt | 8 + .../java/com/ivy/wallet/ui/csv/CSVScreen.kt | 222 ++++++++++++++++++ .../java/com/ivy/wallet/ui/csv/CSVState.kt | 57 +++++ .../com/ivy/wallet/ui/csv/CSVViewModel.kt | 45 ++++ 4 files changed, 332 insertions(+) create mode 100644 app/src/main/java/com/ivy/wallet/ui/csv/CSVEvent.kt create mode 100644 app/src/main/java/com/ivy/wallet/ui/csv/CSVScreen.kt create mode 100644 app/src/main/java/com/ivy/wallet/ui/csv/CSVState.kt create mode 100644 app/src/main/java/com/ivy/wallet/ui/csv/CSVViewModel.kt diff --git a/app/src/main/java/com/ivy/wallet/ui/csv/CSVEvent.kt b/app/src/main/java/com/ivy/wallet/ui/csv/CSVEvent.kt new file mode 100644 index 0000000000..36ff3201a6 --- /dev/null +++ b/app/src/main/java/com/ivy/wallet/ui/csv/CSVEvent.kt @@ -0,0 +1,8 @@ +package com.ivy.wallet.ui.csv + +import android.net.Uri + +sealed interface CSVEvent { + data class FilePicked(val uri: Uri) : CSVEvent + +} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/ui/csv/CSVScreen.kt b/app/src/main/java/com/ivy/wallet/ui/csv/CSVScreen.kt new file mode 100644 index 0000000000..e38ec9ab2c --- /dev/null +++ b/app/src/main/java/com/ivy/wallet/ui/csv/CSVScreen.kt @@ -0,0 +1,222 @@ +package com.ivy.wallet.ui.csv + +import android.net.Uri +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.rememberScrollState +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.google.accompanist.insets.systemBarsPadding +import com.ivy.design.l0_system.UI +import com.ivy.design.l0_system.colorAs +import com.ivy.wallet.ui.ivyWalletCtx +import com.ivy.wallet.utils.thenIf + +@Composable +fun CSVScreen() { + val viewModel: CSVViewModel = viewModel() + UI(state = viewModel.uiState(), onEvent = viewModel::onEvent) +} + +@Composable +private fun UI( + state: CSVState, + onEvent: (CSVEvent) -> Unit, +) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .systemBarsPadding(), + contentPadding = PaddingValues( + horizontal = 8.dp, + vertical = 16.dp, + ) + ) { + item(key = "import_btn") { + ImportButton( + onFilePicked = { + onEvent(CSVEvent.FilePicked(it)) + } + ) + } + if (state.csv != null) { + spacer8() + csvTable(state.csv) + } + + } +} + +@Composable +private fun ImportButton( + onFilePicked: (Uri) -> Unit, +) { + val ivyContext = ivyWalletCtx() + Button( + onClick = { + ivyContext.openFile { + onFilePicked(it) + } + } + ) { + Text(text = "Import CSV") + } +} + +fun LazyListScope.spacer8() { + item { + Spacer8() + } +} + +@Composable +fun Spacer8() { + Spacer(modifier = Modifier.height(8.dp)) +} + +private fun LazyListScope.csvTable( + csv: List +) { + itemsIndexed(items = csv) { index, row -> + CSVRow(row = row, header = index == 0, even = index % 2 == 0) + } +} + +@Composable +private fun CSVRow( + row: CSVRow, + header: Boolean, + even: Boolean, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState()) + ) { + row.values.forEach { value -> + CSVCell(text = value, header = header, even = even) + } + } +} + +@Composable +private fun CSVCell( + text: String, + header: Boolean, + even: Boolean +) { + Text( + modifier = Modifier + .width(120.dp) + .border(1.dp, UI.colors.pureInverse) + .thenIf(even) { + this.background(UI.colors.medium) + } + .padding(all = 4.dp), + text = text, + style = UI.typo.nB1, + fontWeight = if (header) FontWeight.ExtraBold else FontWeight.Normal, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) +} + + +private fun LazyListScope.mappingRow( + columns: CSVRow, + mapping: ColumnMapping, + onMapTo: (Int, String) -> Unit, + metadataContent: (@Composable (M) -> Unit)? = null, +) { + item { + Spacer8() + Column( + modifier = Modifier + .fillMaxWidth() + .border( + width = 2.dp, + color = when { + mapping.required && !mapping.success -> UI.colors.red + mapping.success -> UI.colors.green + else -> UI.colors.medium + } + ) + .padding(vertical = 8.dp, horizontal = 4.dp) + ) { + Text( + text = mapping.ivyColumn, + style = UI.typo.b1.colorAs(UI.colors.primary), + ) + Spacer8() + Row( + verticalAlignment = Alignment.CenterVertically + ) { + columns.values.forEachIndexed { index, column -> + Button( + colors = if (column == mapping.name) { + ButtonDefaults.buttonColors() + } else { + ButtonDefaults.outlinedButtonColors() + }, + onClick = { + onMapTo(index, column) + } + ) { + Text(text = column) + } + Spacer(Modifier.width(8.dp)) + } + } + + if (metadataContent != null) { + Spacer8() + metadataContent(mapping.metadata) + } + + if (mapping.sampleValues.isNotEmpty()) { + Spacer8() + CSVRow(row = CSVRow(mapping.sampleValues), header = false, even = true) + } + } + Spacer8() + } +} + +fun LazyListScope.sectionDivider(text: String) { + item { + Spacer8() + Text(text = text, style = UI.typo.h2) + Spacer8() + } +} + +fun LazyListScope.important( + columns: CSVRow, + importantFields: ImportantFields, + onEvent: (CSVEvent) -> Unit +) { + sectionDivider("Important") + mappingRow( + columns = columns, + mapping = importantFields.amount, + onMapTo = { index, name -> + + }, + metadataContent = { + + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/ui/csv/CSVState.kt b/app/src/main/java/com/ivy/wallet/ui/csv/CSVState.kt new file mode 100644 index 0000000000..c6697f10aa --- /dev/null +++ b/app/src/main/java/com/ivy/wallet/ui/csv/CSVState.kt @@ -0,0 +1,57 @@ +package com.ivy.wallet.ui.csv + +import com.ivy.wallet.domain.data.core.Transaction + +data class CSVState( + val columns: CSVRow, + val csv: List?, + val important: ImportantFields?, + val transfer: TransferFields?, + val optional: OptionalFields?, + val transactions: List? +) + +data class ImportantFields( + val amount: ColumnMapping, + val type: ColumnMapping, + val date: ColumnMapping, + val account: ColumnMapping, + val accountCurrency: ColumnMapping, +) + +sealed interface AmountMetadata { + data class Multiple(val multiplier: Int) : AmountMetadata + data class Divider(val divider: Int) : AmountMetadata +} + +data class TrnTypeMetadata( + val income: String, + val expense: String, + val transfer: String?, +) + +data class TransferFields( + val toAccount: ColumnMapping, + val toAccountCurrency: ColumnMapping, +) + +data class OptionalFields( + val category: ColumnMapping, + val title: ColumnMapping, + val description: ColumnMapping, +) + +data class ColumnMapping( + val ivyColumn: String, + val helpInfo: String, + val name: String, + val index: Int, + val sampleValues: List, + val metadata: M, + val required: Boolean, + val success: Boolean, +) + +data class CSVRow( + val values: List +) \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/ui/csv/CSVViewModel.kt b/app/src/main/java/com/ivy/wallet/ui/csv/CSVViewModel.kt new file mode 100644 index 0000000000..20c0a82335 --- /dev/null +++ b/app/src/main/java/com/ivy/wallet/ui/csv/CSVViewModel.kt @@ -0,0 +1,45 @@ +package com.ivy.wallet.ui.csv + +import androidx.compose.runtime.Composable +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class CSVViewModel @Inject constructor( + +) : ViewModel() { + + @Composable + fun uiState(): CSVState = TODO() + + + private suspend fun handleEvent(event: CSVEvent) { + + } + + + // region Boiler-plate + private val events = MutableSharedFlow(replay = 0) + + init { + viewModelScope.launch { + events.collect { + handleEvent(it) + } + } + } + + fun onEvent(event: CSVEvent) { + viewModelScope.launch { + events.emit(event) + } + } + // endregion +} \ No newline at end of file