MyMoney Notes

 MyMoney Notes

Kelas: Pemrograman Perangkat Bergerak B 
Anggota 1 
Nama: Nathanael Valen Susilo  
NRP: 5025231099
Anggota 2
Nama: Stefanus Yosua Mamamoba 
NRP: 5025231066 


Link 

Deskripsi
Aplikasi ini membantu pengguna dalam mencatat pemasukan, pengeluaran, serta menganalisis laporan keuangan berdasarkan transaksi yang sudah tercatat. Setiap penambahan pemasukan dan pengeluaran memiliki kategorinya masing-masing, seperti gaji, transfer masuk, bonus, dan pendapatan lain pada bagian deposit. Sebaliknya, pada bagian pembayaran terdapat kategori, seperti makan & minum, transportasi, belanja, tagihan, dan pengeluaran lain. Setiap transaksi tercatat dalam histori dan pengguna dapat melihat laporan keuangan dalam time frame yang customizable, baik dalam minggu, bulan, maupun tahun.
 
Struktur Repositori
  
 
  • data: direktori ini terdiri dari dua file kotlin, berupa FinanceRepository yang menyimpan logika untuk aplikasi ini dapat berjalan dan TransactionFactory bertugas untuk menginisasi data untuk aplikasi dengan dummy.
  •  model: direktori ini menyimpan struktur data yang digunakan untuk aplikasi, seperti transaksi, kategori, dan tanggal untuk pencatatan.
  • ui: direktori ini memiliki fungsi yang berbeda-beda untuk mengonstruksi tampilan kepada pengguna, seperti components untuk pembuatan chart, filter, dashboard, dan kategori. Selain itu, terdapat screens untuk membuat tampilan dashboard, history, serta report, sedangkan theme bertujuan untuk menyimpan warna, penggunaan font, dan tema yang mengonstruksi aplikasi secara keseluruhan. Terakhir, terdapat utils yang digunakan untuk pembuatan format dan parser aplikasi.
 
 
Fitur 

  • Dashboard

                    

FinanceRepository.kt:

fun addTransaction(
        amount: Int,
        category: FinanceCategory,
        note: String,
        timestampMillis: Long = System.currentTimeMillis()
    ): Result<WalletTransaction> {
        return runCatching {
            require(amount > 0) { "Nominal harus lebih dari 0." }

            val normalizedNote = note.trim()
            val transaction = WalletTransaction(
                id = nextIdState.intValue++,
                title = category.title,
                description = normalizedNote,
                amount = amount,
                type = category.type,
                category = category,
                timestampMillis = timestampMillis
            )

            allTransactionsInternal.add(0, transaction)
            persistToStorage()
            transaction
        }
    }

    fun deleteTransaction(transactionId: Int): Result<Unit> {
        return runCatching {
            val index = allTransactionsInternal.indexOfFirst { it.id == transactionId }
            require(index >= 0) { "Transaksi tidak ditemukan." }

            allTransactionsInternal.removeAt(index)
            persistToStorage()
        }
    }

    fun updateMonthlyLimit(newLimit: Int): Result<Unit> {
        return runCatching {
            require(newLimit > 0) { "Limit harus lebih dari 0." }
            monthlyLimitState.intValue = newLimit
            persistToStorage()
        }
    }

 

Fungsi  addTransactiondeleteTransaction, updateMonthlyLimit merupakan bagian utama untuk membentuk fitur pada dashboard. Tugas dari fungsi-fungsi yang terdapat di FinanceRepository.kt adalah untuk mencatat dan menghapus transaksi, serta mengubah limit uang bulanan yang harus bernilai lebih dari nol.

  • Histori 

                                                                 

HistoryScreen.kt: 

 fun HistoryScreen() {
    val allTransactions = FinanceRepository.allTransactions()
    val groupedByDate = allTransactions.groupBy { formatDate(it.timestampMillis) }

    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(10.dp)
    ) {
        item {
            Text(
                text = "Histori Transaksi",
                style = MaterialTheme.typography.titleLarge,
                color = MyWalletBlack,
                fontWeight = FontWeight.Bold
            )
            Text(
                text = "Terurut dari yang terbaru",
                style = MaterialTheme.typography.bodySmall,
                color = MyWalletTextSecondary,
                modifier = Modifier.padding(top = 2.dp)
            )
        }

        if (allTransactions.isEmpty()) {
            item {
                EmptyHistoryState()
            }
        } else {
            groupedByDate.forEach { (date, transactions) ->
                item {
                    Text(
                        text = date,
                        style = MaterialTheme.typography.labelLarge,
                        color = MyWalletTextSecondary,
                        fontWeight = FontWeight.SemiBold,
                        modifier = Modifier.padding(top = 8.dp)
                    )
                }

                items(transactions, key = { it.id }) { transaction ->
                    TransactionCard(
                        transaction = transaction,
                        onDeleteClick = {
                            FinanceRepository.deleteTransaction(transaction.id)
                        }
                    )
                }
            }
        }
    }
}() {
    val allTransactions = FinanceRepository.allTransactions()
    val groupedByDate = allTransactions.groupBy { formatDate(it.timestampMillis) }

    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(10.dp)
    ) {
        item {
            Text(
                text = "Histori Transaksi",
                style = MaterialTheme.typography.titleLarge,
                color = MyWalletBlack,
                fontWeight = FontWeight.Bold
            )
            Text(
                text = "Terurut dari yang terbaru",
                style = MaterialTheme.typography.bodySmall,
                color = MyWalletTextSecondary,
                modifier = Modifier.padding(top = 2.dp)
            )
        }

        if (allTransactions.isEmpty()) {
            item {
                EmptyHistoryState()
            }
        } else {
            groupedByDate.forEach { (date, transactions) ->
                item {
                    Text(
                        text = date,
                        style = MaterialTheme.typography.labelLarge,
                        color = MyWalletTextSecondary,
                        fontWeight = FontWeight.SemiBold,
                        modifier = Modifier.padding(top = 8.dp)
                    )
                }

                items(transactions, key = { it.id }) { transaction ->
                    TransactionCard(
                        transaction = transaction,
                        onDeleteClick = {
                            FinanceRepository.deleteTransaction(transaction.id)
                        }
                    )
                }
            }
        }
    }
}

Program HistoryScreen ini berfungsi untuk menampilkan daftar riwayat aktivitas keuangan pengguna secara kronologis dengan pengelompokan berbasis waktu. Secara teknis, program mengambil seluruh data dari FinanceRepository dan menerapkan operasi pemetaan data menggunakan fungsi groupBy, yang mengubah daftar transaksi linear menjadi struktur data berkelompok (Map) berdasarkan tanggal yang diformat. Hal ini memungkinkan antarmuka untuk menyajikan informasi dengan lebih terstruktur, di mana setiap tanggal berfungsi sebagai header untuk transaksi yang terjadi pada hari tersebut.

Antarmuka dibangun menggunakan komponen LazyColumn untuk mengoptimalkan performa rendering, terutama saat jumlah transaksi sangat banyak, karena komponen ini hanya akan memproses elemen yang terlihat di layar. Program menyertakan logika kondisional yang secara otomatis beralih ke komponen EmptyHistoryState jika tidak ada data yang ditemukan, guna menjaga pengalaman pengguna tetap informatif. Di dalam daftar tersebut, setiap transaksi dirender menggunakan TransactionCard yang dilengkapi dengan fungsi penghapusan (onDeleteClick), di mana penggunaan key pada daftar memastikan bahwa Compose dapat melacak perubahan posisi atau penghapusan item secara efisien tanpa harus merender ulang seluruh daftar.

  • Laporan

                

ReportScreen.kt:  

fun ReportScreen() {
    val currentYear = FinanceRepository.currentYear()
    val currentMonth = FinanceRepository.currentMonth()

    var selectedType by remember { mutableStateOf<TransactionType?>(TransactionType.INCOME) }
    var selectedPeriod by remember { mutableStateOf(ReportPeriod.MONTH) }
    var selectedMonth by remember { mutableIntStateOf(currentMonth) }
    var selectedYear by remember { mutableIntStateOf(currentYear) }

    val filteredTransactions = FinanceRepository.filterTransactions(
        period = selectedPeriod,
        selectedMonth = selectedMonth,
        selectedYear = selectedYear
    ).filter { transaction ->
        selectedType == null || transaction.type == selectedType
    }

    val reportItems = FinanceRepository.reportCategoryItems(filteredTransactions)
    val totalFlow = reportItems.sumOf { it.amount }

    val incomeTotal = filteredTransactions
        .filter { it.type == TransactionType.INCOME }
        .sumOf { it.amount }

    val expenseTotal = filteredTransactions
        .filter { it.type == TransactionType.EXPENSE }
        .sumOf { it.amount }

    LazyColumn(
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(14.dp)
    ) {
        item {
            Text(
                text = "Laporan Keuangan",
                style = MaterialTheme.typography.titleLarge,
                color = MyWalletBlack,
                fontWeight = FontWeight.Bold
            )
            Text(
                text = periodSubtitle(
                    period = selectedPeriod,
                    month = selectedMonth,
                    year = selectedYear
                ),
                style = MaterialTheme.typography.bodySmall,
                color = MyWalletTextSecondary
            )
        }
        item {
            TransactionTypeSelector(
                selected = selectedType,
                onSelected = { selectedType = it }
            )
        }

        item {
            ReportPeriodSelector(
                selected = selectedPeriod,
                onSelected = { selectedPeriod = it }
            )
        }

        item {
            when (selectedPeriod) {
                ReportPeriod.WEEK -> {
                    WeekFilterInfo()
                }

                ReportPeriod.MONTH -> {
                    MonthSelector(
                        selectedMonth = selectedMonth,
                        onMonthSelected = { selectedMonth = it }
                    )
                }

                ReportPeriod.YEAR -> {
                    YearSelector(
                        years = FinanceRepository.availableYears(),
                        selectedYear = selectedYear,
                        onYearSelected = { selectedYear = it }
                    )
                }
            }
        }

        item {
            DonutReportCard(
                reportItems = reportItems,
                totalFlow = totalFlow,
                incomeTotal = incomeTotal,
                expenseTotal = expenseTotal
            )
        }

        item {
            Text(
                text = "Kategori Transaksi",
                style = MaterialTheme.typography.titleMedium,
                fontWeight = FontWeight.SemiBold,
                color = MyWalletBlack
            )
        }
        item { ReportCategoryGrid(reportItems = reportItems) }
    }
}

Program ReportScreen ini merupakan komponen antarmuka laporan keuangan yang dirancang menggunakan Jetpack Compose dengan fokus pada fleksibilitas penyaringan data secara real-time. Di bagian awal, fungsi ini menginisialisasi berbagai status (state) seperti tipe transaksi, periode laporan (mingguan, bulanan, atau tahunan), serta penanda waktu yang sinkron dengan repositori data keuangan. Program menerapkan logika reaktif di mana variabel filteredTransactions akan secara otomatis menghitung ulang daftar transaksi setiap kali pengguna mengubah filter pada UI, yang kemudian diproses lebih lanjut untuk menghasilkan akumulasi total pemasukan, pengeluaran, serta distribusi per kategori.

Struktur tampilan disusun menggunakan LazyColumn untuk memastikan antarmuka tetap responsif dan efisien saat memuat elemen dalam jumlah banyak. Komponen ini mengintegrasikan berbagai kontrol seleksi, seperti pemilih tipe transaksi dan pemilih periode yang secara dinamis mengubah kontrol input di bawahnya misalnya menampilkan MonthSelector hanya ketika periode bulanan dipilih. Sebagai representasi visual utama, program memanggil DonutReportCard untuk menyajikan data dalam bentuk grafik donat serta ReportCategoryGrid untuk merinci performa keuangan berdasarkan kategori, sehingga memberikan gambaran komprehensif mengenai arus kas pengguna dalam satu layar yang terintegrasi.

Comments

Popular posts from this blog