Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ jobs:
- name: Make Gradle wrapper executable
run: chmod +x ./gradlew

- name: Run Detekt
run: ./gradlew detekt

- name: Compile App
run: ./gradlew :app:compileFossDebugKotlin :app:compileWearDebugKotlin :wear:compileDebugKotlin :sync-contract:compileKotlin

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ jobs:
run: |
echo "$MINUS_RELEASE_KEYSTORE_BASE64" | base64 --decode > release-keystore.jks

- name: Run Detekt
run: ./gradlew detekt

- name: Run Fastlane release lane
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@
.externalNativeBuild
.cxx
local.properties

# Detekt
config/detekt/detekt-baseline.xml
build/reports/detekt/
42 changes: 35 additions & 7 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,45 @@ plugins {
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.paparazzi)
alias(libs.plugins.detekt)

id("dagger.hilt.android.plugin")
id("com.google.devtools.ksp")
}

detekt {
buildUponDefaultConfig = true
config.setFrom(files("$rootDir/config/detekt/detekt.yml"))
baseline = file("$rootDir/config/detekt/detekt-baseline.xml")
// Disable all default rulesets so only rules in detekt.yml are active.
// Formatting rules (Indentation, MaxLineLength, etc.) are part of default
// rulesets and can't be selectively disabled in the Gradle plugin — any
// formatting issues present in the codebase are suppressed via the baseline.
disableDefaultRuleSets = true
}

// Configure reports on the task level (avoids deprecated extension-level API)
tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
reports {
html.required.set(true)
xml.required.set(true)
}
}

// Paparazzi snapshot tests run in the test JVM. These system properties must be
// passed explicitly to that JVM (gradle.properties alone only affects Gradle's
// own JVM, not forked test JVMs).
//
// `app.cash.paparazzi.differ=offbytwo` — allows 2-pixel offset per pixel
// `paparazzi.maxPercentDifferenceDefault` — up to 5% of pixels may differ
//
// Together these absorb cross-platform rasterisation drift (Linux CI vs
// Windows/macOS dev machines) without hiding real layout regressions.
tasks.withType<Test>().configureEach {
systemProperty("app.cash.paparazzi.differ", "offbytwo")
systemProperty("paparazzi.maxPercentDifferenceDefault", "5.0")
}

fun gitOutput(vararg args: String): String? {
return runCatching {
val process = ProcessBuilder("git", *args)
Expand Down Expand Up @@ -185,7 +219,6 @@ dependencies {
implementation(libs.androidx.compose.animation)
implementation(libs.androidx.compose.ui.tooling.preview.v106)
implementation(libs.androidx.datastore.preferences)
implementation(libs.androidx.recyclerview)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.room.paging)
Expand All @@ -197,6 +230,7 @@ dependencies {
implementation(libs.androidx.glance.appwidget)
implementation(libs.androidx.glance.appwidget.preview)
implementation(libs.androidx.glance.preview)
implementation(libs.androidx.glance.material3)
implementation(libs.androidx.core.splashscreen)
implementation(libs.accompanist.systemuicontroller)
implementation(libs.dagger)
Expand All @@ -208,10 +242,6 @@ dependencies {
ksp(libs.dagger.compiler)
ksp(libs.hilt.androidcompiler)

// Glance
implementation(libs.androidx.glance.appwidget)
implementation(libs.androidx.glance.material3)

// WorkManager for notifications
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.kotlinx.serialization.json)
Expand All @@ -223,6 +253,4 @@ dependencies {

debugImplementation(libs.androidx.compose.ui.tooling.v106)
debugImplementation(libs.androidx.compose.ui.testmanifest.v183)

implementation(libs.androidx.compose.material3.windowsize)
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
package com.serranoie.app.minus

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.serranoie.app.minus", appContext.packageName)
}
}
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.serranoie.app.minus", appContext.packageName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import javax.inject.Singleton

@Singleton
class WearableService @Inject constructor() {
fun getReachableSenderNodeIds(): List<String> = emptyList()
fun getReachableSenderNodeIds(): List<String> = emptyList()
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import javax.inject.Singleton

@Singleton
class PhoneWearMessageListener @Inject constructor() {
fun start() = Unit
fun start() = Unit
}
Binary file not shown.
67 changes: 20 additions & 47 deletions app/src/main/java/com/serranoie/app/minus/MinusApplication.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.serranoie.app.minus

import android.app.Activity
import android.app.Application
import android.os.Bundle
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import com.serranoie.app.minus.wearsync.PhoneWearMessageListener
Expand All @@ -14,53 +12,28 @@ import javax.inject.Inject
@HiltAndroidApp
class MinusApplication : Application(), Configuration.Provider {

@Inject
lateinit var workerFactory: HiltWorkerFactory
@Inject
lateinit var workerFactory: HiltWorkerFactory

@Inject
lateinit var phoneWearMessageListener: PhoneWearMessageListener
@Inject
lateinit var phoneWearMessageListener: PhoneWearMessageListener

override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()

override fun onCreate() {
super.onCreate()
// Log all priorities in debug builds, no-op in release builds.
AndroidLogcatLogger.installOnDebuggableApp(this, minPriority = LogPriority.VERBOSE)
override fun onCreate() {
super.onCreate()
AndroidLogcatLogger.installOnDebuggableApp(this, minPriority = LogPriority.VERBOSE)

phoneWearMessageListener.start()
phoneWearMessageListener.start()

registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {

}

override fun onActivityStarted(activity: Activity) {

}

override fun onActivityResumed(activity: Activity) {

}

override fun onActivityPaused(activity: Activity) {
// ExtendWidgetReceiver.requestUpdateData(activity.applicationContext)
// MinimalWidgetReceiver.requestUpdateData(activity.applicationContext)
}

override fun onActivityStopped(activity: Activity) {

}

override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {

}

override fun onActivityDestroyed(activity: Activity) {

}
})
}
}
// registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
// override fun onActivityPaused(activity: Activity) {
// ExtendWidgetReceiver.requestUpdateData(activity.applicationContext)
// MinimalWidgetReceiver.requestUpdateData(activity.applicationContext)
// }
// })
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.serranoie.app.minus.data.csv

import android.content.Context
import androidx.core.net.toUri
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.Data
Expand All @@ -21,7 +22,7 @@ class CsvImportWorker @AssistedInject constructor(
)

return runCatching {
applicationContext.contentResolver.openInputStream(android.net.Uri.parse(uriString)).use { stream ->
applicationContext.contentResolver.openInputStream(uriString.toUri()).use { stream ->
if (stream == null) {
return Result.failure(
Data.Builder().putString(KEY_ERROR, "Unable to open CSV stream").build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ object MinusCsvContract {
const val COL_ID = "id"
const val COL_IS_CREDIT = "is_credit"

// Budget metadata (repeated in a metadata row, and compatible with older exports)
const val COL_BUDGET_TOTAL = "budget_total"
const val COL_BUDGET_PERIOD = "budget_period"
const val COL_BUDGET_START_DATE = "budget_start_date"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,4 @@ class MinusCsvExporter {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ class MinusCsvParser {
if (row.amount <= BigDecimal.ZERO) {
errors.add("Line $lineNo discarded: amount must be > 0")
null
} else row
} else {
row
}
}.onSuccess { parsed ->
if (parsed != null) rows.add(parsed)
}.onFailure { throwable ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import javax.inject.Singleton
@Singleton
class MinusCsvService @Inject constructor(
private val repository: BudgetRepository,
@ApplicationContext private val context: Context,
@param:ApplicationContext private val context: Context,
) {

private val parser = MinusCsvParser()
Expand All @@ -28,16 +28,16 @@ class MinusCsvService @Inject constructor(
suspend fun getExportFileName(): String {
val settings = repository.getBudgetSettingsSync() ?: return MinusCsvContract.FILE_NAME
val periodCount = repository.getPeriodCount()
// If current period has no transactions yet, count might be one lower than expected

// If current period has no transactions yet, count might be one lower than expected
// for the "current" label, but user asked for "sequence number of the period".
// If they are in their 5th period, we use BP5.
val bpLabel = "BP${periodCount + 1}"

val startDate = settings.startDate.format(fileDateFormatter).lowercase()
val endDate = settings.getPeriodEndDate().format(fileDateFormatter).lowercase()

return "minus_backup-${bpLabel}_${startDate}-${endDate}.csv"
return "minus_backup-${bpLabel}_$startDate-$endDate.csv"
}

suspend fun exportAllTransactions(outputStream: OutputStream) {
Expand Down
46 changes: 23 additions & 23 deletions app/src/main/java/com/serranoie/app/minus/data/di/DatabaseModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
object DatabaseModule {

@Provides
@Singleton
fun provideAppDatabase(
@ApplicationContext context: Context
): AppDatabase {
return Room.databaseBuilder(
@Provides
@Singleton
fun provideAppDatabase(
@ApplicationContext context: Context
): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
AppDatabase.DATABASE_NAME
Expand All @@ -34,25 +34,25 @@ object DatabaseModule {
.addMigrations(AppDatabaseMigrations.MIGRATION_9_10)
.fallbackToDestructiveMigration()
.build()
}
}

@Provides
fun provideTransactionDao(database: AppDatabase): TransactionDao {
return database.transactionDao()
}
@Provides
fun provideTransactionDao(database: AppDatabase): TransactionDao {
return database.transactionDao()
}

@Provides
fun provideBudgetSettingsDao(database: AppDatabase): BudgetSettingsDao {
return database.budgetSettingsDao()
}
@Provides
fun provideBudgetSettingsDao(database: AppDatabase): BudgetSettingsDao {
return database.budgetSettingsDao()
}

@Provides
fun provideCategoryDao(database: AppDatabase): CategoryDao {
return database.categoryDao()
}
@Provides
fun provideCategoryDao(database: AppDatabase): CategoryDao {
return database.categoryDao()
}

@Provides
fun provideQueuedTransactionDao(database: AppDatabase): QueuedTransactionDao {
return database.queuedTransactionDao()
}
@Provides
fun provideQueuedTransactionDao(database: AppDatabase): QueuedTransactionDao {
return database.queuedTransactionDao()
}
}
Loading
Loading