diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..bb1abef
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,53 @@
+name: Build & Release APK
+on:
+ pull_request:
+ branches: [main]
+ paths:
+ - "**/*.kt"
+ - "**/*.xml"
+ - "**/*.gradle.kts"
+ - "gradle/libs.versions.toml"
+ - "gradle/wrapper/**"
+ - "gradle.properties"
+ workflow_dispatch:
+permissions:
+ contents: write
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Check version is new
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ VERSION=$(grep 'versionName' app/build.gradle.kts | grep -oP '"[^"]+"' | tr -d '"')
+ echo "VERSION=${VERSION}" >> $GITHUB_ENV
+ if gh release view "v${VERSION}" &>/dev/null; then
+ echo "::error::v${VERSION} already released."
+ exit 1
+ fi
+ - uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ - uses: gradle/actions/setup-gradle@v3
+ with:
+ gradle-version: '8.13'
+ cache-read-only: false
+ - run: echo "sdk.dir=$ANDROID_SDK_ROOT" > local.properties
+ - run: gradle :app:assembleDebug --no-daemon
+ - run: gradle :app:test --no-daemon
+ - run: gradle :app:lintDebug --no-daemon
+ - name: Create GitHub Release
+ if: github.event_name == 'workflow_dispatch'
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ APK_NAME="Dash-${{ env.VERSION }}-debug.apk"
+ mv app/build/outputs/apk/debug/app-debug.apk \
+ app/build/outputs/apk/debug/${APK_NAME}
+ gh release create "v${{ env.VERSION }}" \
+ "app/build/outputs/apk/debug/${APK_NAME}" \
+ --title "Dash v${{ env.VERSION }}" \
+ --notes "Debug build — enable 'Install unknown apps' in Android settings to sideload."
diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml
new file mode 100644
index 0000000..c4034b5
--- /dev/null
+++ b/.github/workflows/changelog-check.yml
@@ -0,0 +1,27 @@
+name: Changelog bump check
+on:
+ pull_request:
+ branches: [main]
+ paths:
+ - "**/*.kt"
+ - "**/*.xml"
+ - "**/*.gradle.kts"
+ - "gradle/libs.versions.toml"
+permissions:
+ contents: read
+jobs:
+ check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Fail if CHANGELOG.md was not updated
+ run: |
+ CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
+ if echo "$CHANGED" | grep -q "^CHANGELOG.md$"; then
+ echo "CHANGELOG.md updated — good."
+ else
+ echo "::error::Code changed but CHANGELOG.md was not updated."
+ exit 1
+ fi
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..667b219
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,35 @@
+name: "CodeQL"
+on:
+ push:
+ branches: ["main"]
+ pull_request:
+ branches: ["main"]
+ schedule:
+ - cron: "37 19 * * 2"
+permissions:
+ security-events: write
+ actions: read
+ contents: read
+jobs:
+ analyze:
+ name: Analyze (java-kotlin)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: github/codeql-action/init@v4
+ with:
+ languages: java-kotlin
+ queries: security-extended
+ - uses: actions/setup-java@v4
+ with:
+ java-version: "17"
+ distribution: temurin
+ - uses: gradle/actions/setup-gradle@v3
+ with:
+ gradle-version: '8.13'
+ cache-read-only: false
+ - run: echo "sdk.dir=$ANDROID_SDK_ROOT" > local.properties
+ - run: gradle :app:assembleDebug --no-daemon
+ - uses: github/codeql-action/analyze@v4
+ with:
+ category: /language:java-kotlin
diff --git a/.github/workflows/license-sync.yml b/.github/workflows/license-sync.yml
new file mode 100644
index 0000000..01aedac
--- /dev/null
+++ b/.github/workflows/license-sync.yml
@@ -0,0 +1,24 @@
+name: Licence screen sync check
+on:
+ pull_request:
+ branches: [main]
+ paths:
+ - "gradle/libs.versions.toml"
+permissions:
+ contents: read
+jobs:
+ check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Fail if LicensesScreen.kt was not updated
+ run: |
+ CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
+ if echo "$CHANGED" | grep -q "LicensesScreen.kt"; then
+ echo "LicensesScreen.kt updated — good."
+ else
+ echo "::error::gradle/libs.versions.toml changed but LicensesScreen.kt was not updated."
+ exit 1
+ fi
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e5b1b4c..bb9c8f0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,24 +1,40 @@
# Changelog
-All notable changes to the Android app are documented here.
+All notable changes to this project will be documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
----
-
## [Unreleased]
+### Fixed
+- CI: provision Gradle 8.13 directly via `gradle/actions/setup-gradle` instead of
+ relying on `gradle-wrapper.jar`, which cannot be committed through the GitHub API
+- Compose BOM bumped to 2024.09.00 (Material3 1.3.0) to gain `PullToRefreshBox`
+ and align `ModalBottomSheetProperties` signature (removed `securePolicy` param)
+- Added missing `lifecycle-runtime-compose` dependency for `collectAsStateWithLifecycle`
+- `TaskRepository.pendingReminders`: replaced unavailable `isNull()` DSL call with
+ a Kotlin-side `completedAt == null` filter
+- `EditTaskSheet`: fixed `SecureFlagPolicy` import (`material3` → `compose.ui.window`)
+- `SettingsViewModel.clearSaveError`: fixed assignment-as-expression syntax error
+- Removed custom `debug.keystore` signing config; CI now uses the default Android
+ debug keystore so packaging does not fail in a clean environment
+- `menuAnchor()` calls updated to `menuAnchor(MenuAnchorType)` in `EditChoreSheet`,
+ `SettingsScreen`, and `EditTaskSheet` — Material3 1.3.0 deprecated the
+ parameterless overload at `DeprecationLevel.ERROR`
+
## [1.0.0] — 2026-05-30
### Added
-- choreDash tab: chore list with staleness indicators, NFC tap-to-log,
- swipe-to-log with undo, group-by-category, archive/unarchive
-- taskDash tab: task list with priority/due/created sort, filter chips
- (All / Active / Done), group-by-category, collapsible done section,
- task reminders via AlarmManager (exact alarms)
-- Settings tab: Supabase URL + anon-key entry, owner selection,
- light/dark/system theme toggle
+- Initial Android app combining choreDash (NFC chore tracker) and taskDash
+ (shared to-do list), connecting to the same Supabase project as the web app
+- choreDash tab: chore list with staleness colour bars, NFC tap-to-log,
+ swipe-to-log with undo snackbar, group-by-category, archive/unarchive
+- taskDash tab: task list with priority/due/created sort, All/Active/Done
+ filter chips, group-by-category, collapsible done section, per-task
+ AlarmManager reminders, owner filter
+- Settings tab: Supabase URL + anon-key, owner dropdown, light/dark/system
+ theme toggle, open-source licenses screen
- NFC foreground dispatch in MainActivity; NDEF text/URI/raw-hex tag-ID extraction
-- DailyStaleChoreWorker: once-a-day notification summarising overdue chores
-- BootWorker: re-schedules pending task reminders after device reboot
-- Open-source licenses screen
-- Material3 dynamic-color theming seeded from #4A7C59 (sage green)
+- DailyStaleChoreWorker (WorkManager periodic): daily overdue-chore notification
+- BootWorker: re-schedules pending task reminders after reboot
+- Material3 theme seeded from `#4A7C59` (sage green)
+- CI workflows: build + release APK, changelog check, license-sync check, CodeQL
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5f3a415
--- /dev/null
+++ b/README.md
@@ -0,0 +1,107 @@
+# choreDash + taskDash — Android
+
+Native Android app combining two household tools:
+
+- **choreDash** — chore tracker with NFC tag support. Tap a tag on the fridge, washing machine, etc. to log that chore as done.
+- **taskDash** — shared to-do list with categories, priority, due dates, and per-task reminder notifications.
+
+Both tools read from / write to the same Supabase project used by the web app.
+Credentials are entered once in the **Settings** tab and persisted in DataStore.
+
+---
+
+## Requirements
+
+| Tool | Version |
+|------|---------|
+| Android Studio | Hedgehog 2023.1+ |
+| JDK | 17 |
+| Android Gradle Plugin | 8.13.x |
+| Compile SDK | 35 |
+| Min SDK | 26 (Android 8) |
+
+---
+
+## Build
+
+```bash
+# Debug APK
+./gradlew assembleDebug
+
+# Run unit tests
+./gradlew test
+
+# Lint
+./gradlew lintDebug
+```
+
+---
+
+## Project structure
+
+```
+app/src/main/java/com/mapgie/dash/
+ DashApplication.kt # HiltAndroidApp + WorkManager Configuration.Provider
+ MainActivity.kt # NFC foreground dispatch, Compose entry point
+ alarm/
+ AlarmReceiver.kt # BroadcastReceiver for task reminders
+ AlarmScheduler.kt # AlarmManager wrapper
+ BootReceiver.kt # Re-schedules alarms after reboot
+ BootWorker.kt # HiltWorker: queries pending reminders on boot
+ DailyStaleChoreWorker.kt # HiltWorker: daily stale-chore notification
+ data/
+ model/ # Chore.kt, Task.kt, Owner.kt + enums/extension fns
+ preferences/ # SettingsRepository (DataStore)
+ repository/ # ChoreRepository, TaskRepository (Supabase)
+ supabase/ # SupabaseClientProvider
+ di/
+ AppModule.kt # Hilt modules
+ notification/
+ NotificationHelper.kt # Channel creation + show helpers
+ nfc/
+ NfcHandler.kt # NDEF/URI/raw-hex tag-ID extraction
+ ui/
+ theme/ # Color.kt, Theme.kt, Type.kt
+ navigation/ # DashNavGraph.kt
+ screens/
+ chores/ # ChoreListViewModel + ChoreListScreen
+ tasks/ # TaskListViewModel + TaskListScreen
+ settings/ # SettingsViewModel + SettingsScreen
+ licenses/ # LicensesScreen
+ components/ # ChoreCard, LogBottomSheet, EditChoreSheet,
+ # TaskCard, EditTaskSheet
+```
+
+---
+
+## NFC setup
+
+1. Write NDEF Text records to your NFC tags (any NFC writer app).
+2. The tag ID is used to identify a chore — see `NfcHandler.extractTagId()`.
+3. When the app receives an NFC intent, `LogBottomSheet` opens pre-filled with the matching chore (or shows "Unknown tag" if no chore matches).
+
+---
+
+## Supabase schema
+
+This app connects to the same Supabase project as the web app.
+Expected tables: `chores`, `chore_logs`, `todos`, `owners`.
+
+---
+
+## Binary files
+
+`app/debug.keystore` and `gradle/wrapper/gradle-wrapper.jar` are binary files.
+After cloning, copy them from a local choreDash checkout or generate a new debug keystore with:
+
+```bash
+keytool -genkey -v -keystore app/debug.keystore -alias androiddebugkey \
+ -keyalg RSA -keysize 2048 -validity 10000 \
+ -storepass android -keypass android -dname "CN=Android Debug,O=Android,C=US"
+```
+
+---
+
+## Open-source licenses
+
+See **Settings → Open-source licenses** inside the app.
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index d692459..ec60368 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -20,19 +20,7 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
- signingConfigs {
- named("debug") {
- storeFile = file("debug.keystore")
- storePassword = "android"
- keyAlias = "androiddebugkey"
- keyPassword = "android"
- }
- }
-
buildTypes {
- debug {
- signingConfig = signingConfigs.getByName("debug")
- }
release {
isMinifyEnabled = true
proguardFiles(
@@ -60,6 +48,7 @@ android {
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
+ implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 03822b3..c0eb0a1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,7 +1,6 @@
-
@@ -9,10 +8,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
+ android:exported="false" tools:node="merge">
+
-
-
-
+
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/DashApplication.kt b/app/src/main/java/com/mapgie/dash/DashApplication.kt
index d87fa41..352cea8 100644
--- a/app/src/main/java/com/mapgie/dash/DashApplication.kt
+++ b/app/src/main/java/com/mapgie/dash/DashApplication.kt
@@ -1,5 +1,4 @@
package com.mapgie.dash
-
import android.app.Application
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
@@ -14,26 +13,17 @@ import javax.inject.Inject
@HiltAndroidApp
class DashApplication : Application(), Configuration.Provider {
-
@Inject lateinit var workerFactory: HiltWorkerFactory
-
override val workManagerConfiguration: Configuration
- get() = Configuration.Builder()
- .setWorkerFactory(workerFactory)
- .build()
-
+ get() = Configuration.Builder().setWorkerFactory(workerFactory).build()
override fun onCreate() {
super.onCreate()
NotificationHelper.createChannels(this)
scheduleDailyChoreCheck()
}
-
private fun scheduleDailyChoreCheck() {
val request = PeriodicWorkRequestBuilder(1, TimeUnit.DAYS).build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
- "daily_chore_check",
- ExistingPeriodicWorkPolicy.KEEP,
- request
- )
+ "daily_chore_check", ExistingPeriodicWorkPolicy.KEEP, request)
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/MainActivity.kt b/app/src/main/java/com/mapgie/dash/MainActivity.kt
index 80eced8..93c5953 100644
--- a/app/src/main/java/com/mapgie/dash/MainActivity.kt
+++ b/app/src/main/java/com/mapgie/dash/MainActivity.kt
@@ -107,4 +107,4 @@ class MainActivity : ComponentActivity() {
super.onPause()
nfcAdapter?.disableForegroundDispatch(this)
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/alarm/AlarmReceiver.kt b/app/src/main/java/com/mapgie/dash/alarm/AlarmReceiver.kt
index 6a8d816..32fab99 100644
--- a/app/src/main/java/com/mapgie/dash/alarm/AlarmReceiver.kt
+++ b/app/src/main/java/com/mapgie/dash/alarm/AlarmReceiver.kt
@@ -34,4 +34,4 @@ class AlarmReceiver : BroadcastReceiver() {
}
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/alarm/AlarmScheduler.kt b/app/src/main/java/com/mapgie/dash/alarm/AlarmScheduler.kt
index c9f91ef..b1c8456 100644
--- a/app/src/main/java/com/mapgie/dash/alarm/AlarmScheduler.kt
+++ b/app/src/main/java/com/mapgie/dash/alarm/AlarmScheduler.kt
@@ -55,4 +55,4 @@ class AlarmScheduler @Inject constructor(
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/alarm/BootReceiver.kt b/app/src/main/java/com/mapgie/dash/alarm/BootReceiver.kt
index fc721c4..5ffd250 100644
--- a/app/src/main/java/com/mapgie/dash/alarm/BootReceiver.kt
+++ b/app/src/main/java/com/mapgie/dash/alarm/BootReceiver.kt
@@ -17,4 +17,4 @@ class BootReceiver : BroadcastReceiver() {
WorkManager.getInstance(context)
.enqueue(OneTimeWorkRequestBuilder().build())
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/alarm/BootWorker.kt b/app/src/main/java/com/mapgie/dash/alarm/BootWorker.kt
index 110bd91..4c8a579 100644
--- a/app/src/main/java/com/mapgie/dash/alarm/BootWorker.kt
+++ b/app/src/main/java/com/mapgie/dash/alarm/BootWorker.kt
@@ -25,4 +25,4 @@ class BootWorker @AssistedInject constructor(
}
Result.success()
}.getOrElse { Result.retry() }
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/alarm/DailyStaleChoreWorker.kt b/app/src/main/java/com/mapgie/dash/alarm/DailyStaleChoreWorker.kt
index e43e678..97197ef 100644
--- a/app/src/main/java/com/mapgie/dash/alarm/DailyStaleChoreWorker.kt
+++ b/app/src/main/java/com/mapgie/dash/alarm/DailyStaleChoreWorker.kt
@@ -25,4 +25,4 @@ class DailyStaleChoreWorker @AssistedInject constructor(
NotificationHelper.showStaleChoresSummary(applicationContext, staleLabels)
Result.success()
}.getOrElse { Result.retry() }
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/data/repository/ChoreRepository.kt b/app/src/main/java/com/mapgie/dash/data/repository/ChoreRepository.kt
index 5d8665d..0950611 100644
--- a/app/src/main/java/com/mapgie/dash/data/repository/ChoreRepository.kt
+++ b/app/src/main/java/com/mapgie/dash/data/repository/ChoreRepository.kt
@@ -106,7 +106,6 @@ class ChoreRepository @Inject constructor(
}
}
-// Used only internally for the owners query; owners table has just a handle column.
@kotlinx.serialization.Serializable
private data class OwnerDto(@kotlinx.serialization.SerialName("handle") val handle: String)
diff --git a/app/src/main/java/com/mapgie/dash/data/repository/TaskRepository.kt b/app/src/main/java/com/mapgie/dash/data/repository/TaskRepository.kt
index c4198b4..92ddcb3 100644
--- a/app/src/main/java/com/mapgie/dash/data/repository/TaskRepository.kt
+++ b/app/src/main/java/com/mapgie/dash/data/repository/TaskRepository.kt
@@ -62,18 +62,15 @@ class TaskRepository @Inject constructor(
client.from("todos").delete { filter { eq("id", taskId) } }
}
- /** Returns tasks with a future reminder_at that have not yet been reminded. */
suspend fun pendingReminders(): List {
val client = requireClient()
return client.from("todos")
.select {
- filter {
- eq("reminded", false)
- isNull("completed_at")
- }
+ filter { eq("reminded", false) }
}
.decodeList()
.filter { dto ->
+ if (dto.completedAt != null) return@filter false
val reminderAt = dto.reminderAt?.let {
runCatching { Instant.parse(it) }.getOrNull()
} ?: return@filter false
diff --git a/app/src/main/java/com/mapgie/dash/di/AppModule.kt b/app/src/main/java/com/mapgie/dash/di/AppModule.kt
index 1f59e6a..dc96e65 100644
--- a/app/src/main/java/com/mapgie/dash/di/AppModule.kt
+++ b/app/src/main/java/com/mapgie/dash/di/AppModule.kt
@@ -1,12 +1,7 @@
package com.mapgie.dash.di
-
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
-
-// All repositories and infrastructure classes use @Inject constructor and
-// @Singleton, so Hilt auto-provides them without explicit @Provides methods.
-// This module exists as an extension point for future additions.
@Module
@InstallIn(SingletonComponent::class)
-object AppModule
+object AppModule
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/di/SupabaseModule.kt b/app/src/main/java/com/mapgie/dash/di/SupabaseModule.kt
index 9f82262..9597e40 100644
--- a/app/src/main/java/com/mapgie/dash/di/SupabaseModule.kt
+++ b/app/src/main/java/com/mapgie/dash/di/SupabaseModule.kt
@@ -1,11 +1,7 @@
package com.mapgie.dash.di
-
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
-
-// SupabaseClientProvider is @Singleton + @Inject; no manual @Provides needed.
-// This module exists as an extension point for mock clients in tests.
@Module
@InstallIn(SingletonComponent::class)
-object SupabaseModule
+object SupabaseModule
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/nfc/NfcHandler.kt b/app/src/main/java/com/mapgie/dash/nfc/NfcHandler.kt
index 766d227..33fa658 100644
--- a/app/src/main/java/com/mapgie/dash/nfc/NfcHandler.kt
+++ b/app/src/main/java/com/mapgie/dash/nfc/NfcHandler.kt
@@ -55,4 +55,4 @@ object NfcHandler {
}
else -> null
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/notification/NotificationHelper.kt b/app/src/main/java/com/mapgie/dash/notification/NotificationHelper.kt
index ea9b421..102a624 100644
--- a/app/src/main/java/com/mapgie/dash/notification/NotificationHelper.kt
+++ b/app/src/main/java/com/mapgie/dash/notification/NotificationHelper.kt
@@ -92,4 +92,4 @@ object NotificationHelper {
NotificationManagerCompat.from(context).notify(1, notification)
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/ui/components/ChoreCard.kt b/app/src/main/java/com/mapgie/dash/ui/components/ChoreCard.kt
index 088b83c..7b07ec9 100644
--- a/app/src/main/java/com/mapgie/dash/ui/components/ChoreCard.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/components/ChoreCard.kt
@@ -114,4 +114,4 @@ private fun lastScannedText(lastScanned: Instant?): String {
days == 1L -> "Yesterday"
else -> "${days}d ago"
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/ui/components/EditChoreSheet.kt b/app/src/main/java/com/mapgie/dash/ui/components/EditChoreSheet.kt
index a9ce87b..baf05fc 100644
--- a/app/src/main/java/com/mapgie/dash/ui/components/EditChoreSheet.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/components/EditChoreSheet.kt
@@ -7,7 +7,6 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.SecureFlagPolicy
import com.mapgie.dash.data.model.Chore
import kotlinx.coroutines.launch
@@ -34,7 +33,6 @@ fun EditChoreSheet(
onDismissRequest = { sheetScope.launch { sheetState.show() } },
sheetState = sheetState,
properties = ModalBottomSheetProperties(
- securePolicy = SecureFlagPolicy.Inherit,
isFocusable = true,
shouldDismissOnBackPress = false
)
@@ -72,7 +70,7 @@ fun EditChoreSheet(
label = { Text("Owner") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = ownerExpanded) },
modifier = Modifier
- .menuAnchor()
+ .menuAnchor(MenuAnchorType.PrimaryNotEditable)
.fillMaxWidth()
)
ExposedDropdownMenu(
@@ -162,4 +160,4 @@ fun EditChoreSheet(
}
)
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/ui/components/EditTaskSheet.kt b/app/src/main/java/com/mapgie/dash/ui/components/EditTaskSheet.kt
index 842ec15..9446c72 100644
--- a/app/src/main/java/com/mapgie/dash/ui/components/EditTaskSheet.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/components/EditTaskSheet.kt
@@ -21,11 +21,11 @@ import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.ModalBottomSheetProperties
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.SecureFlagPolicy
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@@ -150,7 +150,6 @@ fun EditTaskSheet(
onDismissRequest = { sheetScope.launch { sheetState.show() } },
sheetState = sheetState,
properties = ModalBottomSheetProperties(
- securePolicy = SecureFlagPolicy.Inherit,
isFocusable = true,
shouldDismissOnBackPress = false
)
@@ -189,7 +188,7 @@ fun EditTaskSheet(
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = categoryExpanded) },
modifier = Modifier
.fillMaxWidth()
- .menuAnchor()
+ .menuAnchor(MenuAnchorType.PrimaryEditable)
)
val filtered = categories.filter { it.contains(category, ignoreCase = true) }
if (filtered.isNotEmpty()) {
@@ -298,7 +297,7 @@ fun EditTaskSheet(
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = ownerExpanded) },
modifier = Modifier
.fillMaxWidth()
- .menuAnchor()
+ .menuAnchor(MenuAnchorType.PrimaryEditable)
)
if (owners.isNotEmpty()) {
ExposedDropdownMenu(
@@ -446,4 +445,4 @@ fun EditTaskSheet(
}
) { DatePicker(state = reminderPickerState) }
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/ui/components/LogBottomSheet.kt b/app/src/main/java/com/mapgie/dash/ui/components/LogBottomSheet.kt
index 824c11d..ee723c3 100644
--- a/app/src/main/java/com/mapgie/dash/ui/components/LogBottomSheet.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/components/LogBottomSheet.kt
@@ -6,7 +6,6 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.SecureFlagPolicy
import com.mapgie.dash.data.model.Chore
import kotlinx.coroutines.launch
import java.time.Instant
@@ -29,13 +28,10 @@ fun LogBottomSheet(
var selectedMinute by remember { mutableIntStateOf(LocalTime.now().minute) }
var showDatePicker by remember { mutableStateOf(false) }
- // Bounce sheet back on backdrop tap or swipe — prevents the "stuck invisible overlay"
- // issue documented in GoFlo LESSONS.md. User can only exit via explicit Cancel/Confirm.
ModalBottomSheet(
onDismissRequest = { sheetScope.launch { sheetState.show() } },
sheetState = sheetState,
properties = ModalBottomSheetProperties(
- securePolicy = SecureFlagPolicy.Inherit,
isFocusable = true,
shouldDismissOnBackPress = false
)
diff --git a/app/src/main/java/com/mapgie/dash/ui/components/TaskCard.kt b/app/src/main/java/com/mapgie/dash/ui/components/TaskCard.kt
index e1b061a..3e96de2 100644
--- a/app/src/main/java/com/mapgie/dash/ui/components/TaskCard.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/components/TaskCard.kt
@@ -172,4 +172,4 @@ private fun DueBadge(task: TaskDto) {
style = MaterialTheme.typography.labelSmall,
color = color
)
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/ui/navigation/DashNavGraph.kt b/app/src/main/java/com/mapgie/dash/ui/navigation/DashNavGraph.kt
index f96fb2b..d6925ac 100644
--- a/app/src/main/java/com/mapgie/dash/ui/navigation/DashNavGraph.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/navigation/DashNavGraph.kt
@@ -103,4 +103,4 @@ fun DashNavGraph(
}
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/ui/screens/chores/ChoreListScreen.kt b/app/src/main/java/com/mapgie/dash/ui/screens/chores/ChoreListScreen.kt
index ffd977e..a68ec4d 100644
--- a/app/src/main/java/com/mapgie/dash/ui/screens/chores/ChoreListScreen.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/screens/chores/ChoreListScreen.kt
@@ -365,4 +365,4 @@ private fun EmptyState(message: String) {
modifier = Modifier.padding(32.dp)
)
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/ui/screens/chores/ChoreListViewModel.kt b/app/src/main/java/com/mapgie/dash/ui/screens/chores/ChoreListViewModel.kt
index 48cc4fd..72849f0 100644
--- a/app/src/main/java/com/mapgie/dash/ui/screens/chores/ChoreListViewModel.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/screens/chores/ChoreListViewModel.kt
@@ -158,4 +158,4 @@ class ChoreListViewModel @Inject constructor(
fun setOwnerFilter(ownerFilter: OwnerFilter) {
_uiState.update { it.copy(ownerFilter = ownerFilter) }
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/ui/screens/licenses/LicensesScreen.kt b/app/src/main/java/com/mapgie/dash/ui/screens/licenses/LicensesScreen.kt
index 568ebba..6e86668 100644
--- a/app/src/main/java/com/mapgie/dash/ui/screens/licenses/LicensesScreen.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/screens/licenses/LicensesScreen.kt
@@ -76,4 +76,4 @@ fun LicensesScreen(onBack: () -> Unit) {
}
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/mapgie/dash/ui/screens/settings/SettingsScreen.kt
index 2857d25..881214c 100644
--- a/app/src/main/java/com/mapgie/dash/ui/screens/settings/SettingsScreen.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/screens/settings/SettingsScreen.kt
@@ -23,6 +23,7 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
@@ -61,14 +62,12 @@ fun SettingsScreen(
val saveError by viewModel.saveError.collectAsState()
val snackbarHost = remember { SnackbarHostState() }
- // Seed local fields once settings load
var url by rememberSaveable(settings?.supabaseUrl) { mutableStateOf(settings?.supabaseUrl ?: "") }
var key by rememberSaveable(settings?.supabaseKey) { mutableStateOf(settings?.supabaseKey ?: "") }
var owner by rememberSaveable(settings?.ownerHandle) { mutableStateOf(settings?.ownerHandle ?: "") }
var keyVisible by remember { mutableStateOf(false) }
var ownerExpanded by remember { mutableStateOf(false) }
- // Load owners when screen is first shown (after potential existing credentials)
LaunchedEffect(Unit) { viewModel.loadOwners() }
LaunchedEffect(saveError) {
@@ -138,7 +137,7 @@ fun SettingsScreen(
},
modifier = Modifier
.fillMaxWidth()
- .menuAnchor()
+ .menuAnchor(MenuAnchorType.PrimaryEditable)
)
if (owners.isNotEmpty()) {
ExposedDropdownMenu(
@@ -212,4 +211,4 @@ private val ThemeMode.label: String
ThemeMode.SYSTEM -> "System"
ThemeMode.LIGHT -> "Light"
ThemeMode.DARK -> "Dark"
- }
+ }
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/ui/screens/settings/SettingsViewModel.kt b/app/src/main/java/com/mapgie/dash/ui/screens/settings/SettingsViewModel.kt
index 5640877..a11f12f 100644
--- a/app/src/main/java/com/mapgie/dash/ui/screens/settings/SettingsViewModel.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/screens/settings/SettingsViewModel.kt
@@ -47,5 +47,5 @@ class SettingsViewModel @Inject constructor(
}
}
- fun clearSaveError() = _saveError.value = null
+ fun clearSaveError() { _saveError.value = null }
}
diff --git a/app/src/main/java/com/mapgie/dash/ui/screens/tasks/TaskListScreen.kt b/app/src/main/java/com/mapgie/dash/ui/screens/tasks/TaskListScreen.kt
index c4e1ff9..693111f 100644
--- a/app/src/main/java/com/mapgie/dash/ui/screens/tasks/TaskListScreen.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/screens/tasks/TaskListScreen.kt
@@ -275,4 +275,4 @@ private fun TaskSort.next(): TaskSort = when (this) {
TaskSort.PRIORITY -> TaskSort.DUE
TaskSort.DUE -> TaskSort.CREATED
TaskSort.CREATED -> TaskSort.PRIORITY
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/ui/screens/tasks/TaskListViewModel.kt b/app/src/main/java/com/mapgie/dash/ui/screens/tasks/TaskListViewModel.kt
index 65237f3..985d092 100644
--- a/app/src/main/java/com/mapgie/dash/ui/screens/tasks/TaskListViewModel.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/screens/tasks/TaskListViewModel.kt
@@ -192,4 +192,4 @@ class TaskListViewModel @Inject constructor(
fun setGroupBy(enabled: Boolean) = _uiState.update { it.copy(groupByCategory = enabled) }
fun setOwnerFilter(f: OwnerFilter) = _uiState.update { it.copy(ownerFilter = f) }
fun clearError() = _uiState.update { it.copy(error = null) }
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/ui/theme/Color.kt b/app/src/main/java/com/mapgie/dash/ui/theme/Color.kt
index c29118d..7ca7a19 100644
--- a/app/src/main/java/com/mapgie/dash/ui/theme/Color.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/theme/Color.kt
@@ -67,4 +67,4 @@ val md_theme_dark_scrim = Color(0xFF000000)
// Semantic status colours referenced by card left-bars
val StatusStale = Color(0xFFBA1A1A)
val StatusAging = Color(0xFFF29900)
-val StatusFresh = Color(0xFF4A7C59)
+val StatusFresh = Color(0xFF4A7C59)
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/ui/theme/Theme.kt b/app/src/main/java/com/mapgie/dash/ui/theme/Theme.kt
index 70b2747..f9bb34e 100644
--- a/app/src/main/java/com/mapgie/dash/ui/theme/Theme.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/theme/Theme.kt
@@ -95,4 +95,4 @@ fun DashTheme(
typography = DashTypography,
content = content
)
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mapgie/dash/ui/theme/Type.kt b/app/src/main/java/com/mapgie/dash/ui/theme/Type.kt
index 52befef..a6b6714 100644
--- a/app/src/main/java/com/mapgie/dash/ui/theme/Type.kt
+++ b/app/src/main/java/com/mapgie/dash/ui/theme/Type.kt
@@ -57,4 +57,4 @@ val DashTypography = Typography(
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
-)
+)
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index d378acd..bbd3e02 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -2,4 +2,4 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index d378acd..bbd3e02 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -2,4 +2,4 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index 79f5cc8..659588b 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1f83e40..c8f132d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -5,4 +5,4 @@
Alerts when a task reminder is due
Overdue chores
Daily summary of overdue chores
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 7d52c1b..170d40b 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index a59c71a..b9e7867 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,7 +8,7 @@ junitVersion = "1.2.1"
espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.8.3"
activityCompose = "1.9.0"
-composeBom = "2024.06.00"
+composeBom = "2024.09.00"
navigationCompose = "2.7.7"
hilt = "2.52"
work = "2.9.1"
@@ -17,7 +17,6 @@ datastore = "1.1.1"
coroutines = "1.8.1"
supabase = "2.6.1"
ktor = "2.3.12"
-swipe = "1.2.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -25,6 +24,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
+androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeKtx" }
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
diff --git a/gradlew b/gradlew
index fc7f6a3..ce48113 100644
--- a/gradlew
+++ b/gradlew
@@ -1,10 +1,31 @@
#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
#
-# Gradle start up script for POSIX generated by "Gradle Init".
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+# Gradle start up script for POSIX generated by "Gradle init"
+##############################################################################
# Attempt to set APP_HOME
+
# Resolve links: $0 may be a link
app_path=$0
+
+# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
@@ -22,7 +43,7 @@ APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
-# Add default JVM options here.
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
@@ -57,6 +78,7 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
@@ -69,54 +91,33 @@ location of your Java installation."
fi
else
JAVACMD=java
- if ! command -v java >/dev/null 2>&1
- then
- die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
- fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
+ ;;
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
-# Collect all arguments for the java command;
-# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-# shell script including quotes and variable substitutions, so put them in
-# double quotes to make sure that they get re-expanded; and
-# * put everything else in single quotes, so that it's not re-expanded.
-
-set -- \
- "-Dorg.gradle.appname=$APP_BASE_NAME" \
- -classpath "$CLASSPATH" \
- org.gradle.wrapper.GradleWrapperMain \
- "$@"
-
-# Stop when "xargs" is not available.
-if ! command -v xargs >/dev/null 2>&1
-then
- die "xargs is not available"
-fi
-
-# Use "xargs" to parse quoted args.
-eval "set -- $(
- printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
- xargs -n1 |
- sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
- tr '\n' ' '
- )" '"$@"'
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS '"$JAVA_OPTS"' '"$GRADLE_OPTS"' '"\"-Dorg.gradle.appname=$APP_BASE_NAME\""' -classpath '"$CLASSPATH"' org.gradle.wrapper.GradleWrapperMain '"$@"'
exec "$JAVACMD" "$@"