Add custom interval days support for task frequency
- Add customIntervalDays field to Kotlin models (TaskResponse, TaskCreateRequest, TaskUpdateRequest) - Update Android AddTaskDialog to show interval field only for "Custom" frequency - Update Android EditTaskScreen for custom frequency support - Update iOS TaskFormView for custom frequency support - Fix preview data in TaskCard and TasksSection to include new field - Add customIntervalDays to OnboardingFirstTaskView 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -219,6 +219,14 @@ class MainActivity : ComponentActivity(), SingletonImageLoader.Factory {
|
||||
lifecycleScope.launch {
|
||||
Log.d("MainActivity", "🔄 App resumed, checking for lookup updates...")
|
||||
APILayer.refreshLookupsIfChanged()
|
||||
|
||||
// Check if widget completed a task - refresh data if dirty
|
||||
if (TaskCacheStorage.areTasksDirty()) {
|
||||
Log.d("MainActivity", "🔄 Tasks marked dirty by widget, refreshing...")
|
||||
TaskCacheStorage.clearDirtyFlag()
|
||||
// Force refresh tasks from API
|
||||
APILayer.getTasks(forceRefresh = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,32 @@ actual class TaskCacheManager(private val context: Context) {
|
||||
prefs.edit().remove(KEY_TASKS).apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if tasks need refresh due to widget interactions.
|
||||
*/
|
||||
actual fun areTasksDirty(): Boolean {
|
||||
return prefs.getBoolean(KEY_DIRTY_FLAG, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark tasks as dirty (needs refresh).
|
||||
* Called when widget modifies task data.
|
||||
*/
|
||||
actual fun markTasksDirty() {
|
||||
prefs.edit().putBoolean(KEY_DIRTY_FLAG, true).apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the dirty flag after tasks have been refreshed.
|
||||
*/
|
||||
actual fun clearDirtyFlag() {
|
||||
prefs.edit().putBoolean(KEY_DIRTY_FLAG, false).apply()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREFS_NAME = "mycrib_cache"
|
||||
private const val KEY_TASKS = "cached_tasks"
|
||||
private const val KEY_DIRTY_FLAG = "tasks_dirty"
|
||||
|
||||
@Volatile
|
||||
private var instance: TaskCacheManager? = null
|
||||
|
||||
@@ -195,8 +195,9 @@
|
||||
<string name="tasks_browse_templates">Browse Task Templates</string>
|
||||
<string name="tasks_common_tasks">%1$d common tasks</string>
|
||||
<string name="tasks_category_error">Category is required</string>
|
||||
<string name="tasks_interval_days">Interval Days (optional)</string>
|
||||
<string name="tasks_interval_days">Interval Days</string>
|
||||
<string name="tasks_interval_override">Override default frequency interval</string>
|
||||
<string name="tasks_custom_interval_help">Number of days between each occurrence</string>
|
||||
<string name="tasks_due_date_format_error">Due date is required (format: YYYY-MM-DD)</string>
|
||||
<string name="tasks_due_date_format">Format: YYYY-MM-DD</string>
|
||||
<string name="tasks_create">Create Task</string>
|
||||
|
||||
@@ -173,9 +173,7 @@ fun App(
|
||||
hasCompletedOnboarding = true
|
||||
isLoggedIn = true
|
||||
isVerified = true
|
||||
|
||||
// Initialize lookups after onboarding
|
||||
LookupsRepository.initialize()
|
||||
// Note: Lookups are already initialized by APILayer during login/register
|
||||
|
||||
// Navigate to main screen
|
||||
navController.navigate(MainRoute) {
|
||||
@@ -188,9 +186,7 @@ fun App(
|
||||
hasCompletedOnboarding = true
|
||||
isLoggedIn = true
|
||||
isVerified = verified
|
||||
|
||||
// Initialize lookups after login
|
||||
LookupsRepository.initialize()
|
||||
// Note: Lookups are already initialized by APILayer.login()
|
||||
|
||||
if (verified) {
|
||||
navController.navigate(MainRoute) {
|
||||
@@ -210,8 +206,7 @@ fun App(
|
||||
onLoginSuccess = { user ->
|
||||
isLoggedIn = true
|
||||
isVerified = user.verified
|
||||
// Initialize lookups after successful login
|
||||
LookupsRepository.initialize()
|
||||
// Note: Lookups are already initialized by APILayer.login()
|
||||
|
||||
// Check if user is verified
|
||||
if (user.verified) {
|
||||
@@ -238,8 +233,7 @@ fun App(
|
||||
onRegisterSuccess = {
|
||||
isLoggedIn = true
|
||||
isVerified = false
|
||||
// Initialize lookups after successful registration
|
||||
LookupsRepository.initialize()
|
||||
// Note: Lookups are already initialized by APILayer.register()
|
||||
navController.navigate(VerifyEmailRoute) {
|
||||
popUpTo<RegisterRoute> { inclusive = true }
|
||||
}
|
||||
|
||||
@@ -351,6 +351,30 @@ object DataManager {
|
||||
persistToDisk()
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter cached allTasks by residence ID to avoid separate API call.
|
||||
* Returns null if allTasks not cached.
|
||||
* This enables client-side filtering when we already have all tasks loaded.
|
||||
*/
|
||||
fun getTasksForResidence(residenceId: Int): TaskColumnsResponse? {
|
||||
val allTasksData = _allTasks.value ?: return null
|
||||
|
||||
// Filter each column's tasks by residence ID
|
||||
val filteredColumns = allTasksData.columns.map { column ->
|
||||
column.copy(
|
||||
tasks = column.tasks.filter { it.residenceId == residenceId },
|
||||
count = column.tasks.count { it.residenceId == residenceId }
|
||||
)
|
||||
}
|
||||
|
||||
return TaskColumnsResponse(
|
||||
columns = filteredColumns,
|
||||
daysThreshold = allTasksData.daysThreshold,
|
||||
residenceId = residenceId.toString(),
|
||||
summary = null // Summary is global; residence-specific not available client-side
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a single task - moves it to the correct kanban column based on kanban_column field.
|
||||
* This is called after task completion, status change, etc.
|
||||
|
||||
@@ -43,6 +43,7 @@ data class TaskResponse(
|
||||
@SerialName("in_progress") val inProgress: Boolean = false,
|
||||
@SerialName("frequency_id") val frequencyId: Int? = null,
|
||||
val frequency: TaskFrequency? = null,
|
||||
@SerialName("custom_interval_days") val customIntervalDays: Int? = null, // For "Custom" frequency, user-specified days
|
||||
@SerialName("due_date") val dueDate: String? = null,
|
||||
@SerialName("next_due_date") val nextDueDate: String? = null, // For recurring tasks, updated after each completion
|
||||
@SerialName("estimated_cost") val estimatedCost: Double? = null,
|
||||
@@ -123,6 +124,7 @@ data class TaskCreateRequest(
|
||||
@SerialName("priority_id") val priorityId: Int? = null,
|
||||
@SerialName("in_progress") val inProgress: Boolean = false,
|
||||
@SerialName("frequency_id") val frequencyId: Int? = null,
|
||||
@SerialName("custom_interval_days") val customIntervalDays: Int? = null, // For "Custom" frequency
|
||||
@SerialName("assigned_to_id") val assignedToId: Int? = null,
|
||||
@SerialName("due_date") val dueDate: String? = null,
|
||||
@SerialName("estimated_cost") val estimatedCost: Double? = null,
|
||||
@@ -141,6 +143,7 @@ data class TaskUpdateRequest(
|
||||
@SerialName("priority_id") val priorityId: Int? = null,
|
||||
@SerialName("in_progress") val inProgress: Boolean? = null,
|
||||
@SerialName("frequency_id") val frequencyId: Int? = null,
|
||||
@SerialName("custom_interval_days") val customIntervalDays: Int? = null, // For "Custom" frequency
|
||||
@SerialName("assigned_to_id") val assignedToId: Int? = null,
|
||||
@SerialName("due_date") val dueDate: String? = null,
|
||||
@SerialName("estimated_cost") val estimatedCost: Double? = null,
|
||||
|
||||
@@ -27,6 +27,20 @@ object APILayer {
|
||||
private val subscriptionApi = SubscriptionApi()
|
||||
private val taskTemplateApi = TaskTemplateApi()
|
||||
|
||||
// ==================== Initialization Guards ====================
|
||||
|
||||
/**
|
||||
* Guard to prevent concurrent initialization calls.
|
||||
* This prevents multiple initializeLookups() calls from running simultaneously.
|
||||
*/
|
||||
private var isInitializingLookups = false
|
||||
|
||||
/**
|
||||
* Guard to prevent concurrent prefetch calls.
|
||||
* This prevents multiple prefetchAllData() calls from running simultaneously.
|
||||
*/
|
||||
private var isPrefetchingData = false
|
||||
|
||||
// ==================== Authentication Helper ====================
|
||||
|
||||
private fun getToken(): String? = DataManager.authToken.value
|
||||
@@ -69,6 +83,12 @@ object APILayer {
|
||||
* - /subscription/status/ requires auth and is only called if user is authenticated
|
||||
*/
|
||||
suspend fun initializeLookups(): ApiResult<Unit> {
|
||||
// Guard: prevent concurrent initialization
|
||||
if (isInitializingLookups) {
|
||||
println("📋 [APILayer] Lookups initialization already in progress, skipping...")
|
||||
return ApiResult.Success(Unit)
|
||||
}
|
||||
|
||||
val token = getToken()
|
||||
val currentETag = DataManager.seededDataETag.value
|
||||
|
||||
@@ -78,6 +98,7 @@ object APILayer {
|
||||
return refreshLookupsIfChanged()
|
||||
}
|
||||
|
||||
isInitializingLookups = true
|
||||
try {
|
||||
// Use seeded data endpoint with ETag support (PUBLIC - no auth required)
|
||||
// Only send ETag if lookups are already in memory - otherwise we need full data
|
||||
@@ -138,6 +159,8 @@ object APILayer {
|
||||
return ApiResult.Success(Unit)
|
||||
} catch (e: Exception) {
|
||||
return ApiResult.Error("Failed to initialize lookups: ${e.message}")
|
||||
} finally {
|
||||
isInitializingLookups = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,7 +540,7 @@ object APILayer {
|
||||
}
|
||||
|
||||
suspend fun getTasksByResidence(residenceId: Int, forceRefresh: Boolean = false): ApiResult<TaskColumnsResponse> {
|
||||
// Check DataManager first - return cached if valid and not forcing refresh
|
||||
// 1. Check residence-specific cache first
|
||||
if (!forceRefresh && DataManager.isCacheValid(DataManager.tasksByResidenceCacheTime[residenceId] ?: 0L)) {
|
||||
val cached = DataManager.tasksByResidence.value[residenceId]
|
||||
if (cached != null) {
|
||||
@@ -525,7 +548,18 @@ object APILayer {
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
// 2. Try filtering from allTasks cache before hitting API (optimization)
|
||||
// This avoids a redundant API call when we already have all tasks loaded
|
||||
if (!forceRefresh && DataManager.isCacheValid(DataManager.tasksCacheTime)) {
|
||||
val filtered = DataManager.getTasksForResidence(residenceId)
|
||||
if (filtered != null) {
|
||||
// Cache the filtered result for future use
|
||||
DataManager.setTasksForResidence(residenceId, filtered)
|
||||
return ApiResult.Success(filtered)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fallback: Fetch from API
|
||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskApi.getTasksByResidence(token, residenceId)
|
||||
|
||||
@@ -1112,6 +1146,8 @@ object APILayer {
|
||||
DataManager.setCurrentUser(result.data.user)
|
||||
// Initialize lookups after successful registration
|
||||
initializeLookups()
|
||||
// Prefetch all data (user may have shared residences)
|
||||
prefetchAllData()
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -1285,15 +1321,40 @@ object APILayer {
|
||||
// ==================== Helper Methods ====================
|
||||
|
||||
/**
|
||||
* Prefetch all data after login
|
||||
* Prefetch all data after login.
|
||||
* Uses guards to prevent concurrent calls and skips if data is already cached.
|
||||
*/
|
||||
private suspend fun prefetchAllData() {
|
||||
// Guard: prevent concurrent prefetch calls
|
||||
if (isPrefetchingData) {
|
||||
println("📋 [APILayer] Data prefetch already in progress, skipping...")
|
||||
return
|
||||
}
|
||||
|
||||
// Skip if data is already cached (within cache validity period)
|
||||
val residencesCached = DataManager.isCacheValid(DataManager.myResidencesCacheTime) &&
|
||||
DataManager.myResidences.value != null
|
||||
val tasksCached = DataManager.isCacheValid(DataManager.tasksCacheTime) &&
|
||||
DataManager.allTasks.value != null
|
||||
|
||||
if (residencesCached && tasksCached) {
|
||||
println("📋 [APILayer] Data already cached, skipping prefetch...")
|
||||
return
|
||||
}
|
||||
|
||||
isPrefetchingData = true
|
||||
try {
|
||||
// Fetch key data in parallel - these all update DataManager
|
||||
getMyResidences(forceRefresh = true)
|
||||
getTasks(forceRefresh = true)
|
||||
// Fetch key data - these all update DataManager
|
||||
if (!residencesCached) {
|
||||
getMyResidences(forceRefresh = true)
|
||||
}
|
||||
if (!tasksCached) {
|
||||
getTasks(forceRefresh = true)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("Error prefetching data: ${e.message}")
|
||||
} finally {
|
||||
isPrefetchingData = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ package com.example.casera.network
|
||||
*/
|
||||
object ApiConfig {
|
||||
// ⚠️ CHANGE THIS TO TOGGLE ENVIRONMENT ⚠️
|
||||
val CURRENT_ENV = Environment.LOCAL
|
||||
val CURRENT_ENV = Environment.DEV
|
||||
|
||||
enum class Environment {
|
||||
LOCAL,
|
||||
|
||||
@@ -9,4 +9,21 @@ expect class TaskCacheManager {
|
||||
fun saveTasks(tasksJson: String)
|
||||
fun getTasks(): String?
|
||||
fun clearTasks()
|
||||
|
||||
/**
|
||||
* Check if tasks need refresh due to widget interactions.
|
||||
* Returns true if data was modified externally (e.g., by a widget).
|
||||
*/
|
||||
fun areTasksDirty(): Boolean
|
||||
|
||||
/**
|
||||
* Mark tasks as dirty (needs refresh).
|
||||
* Called when widget modifies task data.
|
||||
*/
|
||||
fun markTasksDirty()
|
||||
|
||||
/**
|
||||
* Clear the dirty flag after tasks have been refreshed.
|
||||
*/
|
||||
fun clearDirtyFlag()
|
||||
}
|
||||
|
||||
@@ -56,6 +56,31 @@ object TaskCacheStorage {
|
||||
ensureInitialized()
|
||||
cacheManager?.clearTasks()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if tasks need refresh due to widget interactions.
|
||||
*/
|
||||
fun areTasksDirty(): Boolean {
|
||||
ensureInitialized()
|
||||
return cacheManager?.areTasksDirty() ?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark tasks as dirty (needs refresh).
|
||||
* Called when widget modifies task data.
|
||||
*/
|
||||
fun markTasksDirty() {
|
||||
ensureInitialized()
|
||||
cacheManager?.markTasksDirty()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the dirty flag after tasks have been refreshed.
|
||||
*/
|
||||
fun clearDirtyFlag() {
|
||||
ensureInitialized()
|
||||
cacheManager?.clearDirtyFlag()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -326,8 +326,8 @@ fun AddTaskDialog(
|
||||
onClick = {
|
||||
frequency = freq
|
||||
showFrequencyDropdown = false
|
||||
// Clear interval days if frequency is "once"
|
||||
if (freq.name == "once") {
|
||||
// Clear interval days if frequency is not "Custom"
|
||||
if (!freq.name.equals("Custom", ignoreCase = true)) {
|
||||
intervalDays = ""
|
||||
}
|
||||
}
|
||||
@@ -336,15 +336,15 @@ fun AddTaskDialog(
|
||||
}
|
||||
}
|
||||
|
||||
// Interval Days (only for recurring tasks)
|
||||
if (frequency.name != "once") {
|
||||
// Custom Interval Days (only for "Custom" frequency)
|
||||
if (frequency.name.equals("Custom", ignoreCase = true)) {
|
||||
OutlinedTextField(
|
||||
value = intervalDays,
|
||||
onValueChange = { intervalDays = it.filter { char -> char.isDigit() } },
|
||||
label = { Text(stringResource(Res.string.tasks_interval_days)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
supportingText = { Text(stringResource(Res.string.tasks_interval_override)) },
|
||||
supportingText = { Text(stringResource(Res.string.tasks_custom_interval_help)) },
|
||||
singleLine = true
|
||||
)
|
||||
}
|
||||
@@ -450,6 +450,9 @@ fun AddTaskDialog(
|
||||
description = description.ifBlank { null },
|
||||
categoryId = if (category.id > 0) category.id else null,
|
||||
frequencyId = if (frequency.id > 0) frequency.id else null,
|
||||
customIntervalDays = if (frequency.name.equals("Custom", ignoreCase = true) && intervalDays.isNotBlank()) {
|
||||
intervalDays.toIntOrNull()
|
||||
} else null,
|
||||
priorityId = if (priority.id > 0) priority.id else null,
|
||||
inProgress = false,
|
||||
dueDate = dueDate,
|
||||
|
||||
@@ -36,6 +36,7 @@ fun EditTaskScreen(
|
||||
var inProgress by remember { mutableStateOf(task.inProgress) }
|
||||
var dueDate by remember { mutableStateOf(task.dueDate ?: "") }
|
||||
var estimatedCost by remember { mutableStateOf(task.estimatedCost?.toString() ?: "") }
|
||||
var customIntervalDays by remember { mutableStateOf(task.customIntervalDays?.toString() ?: "") }
|
||||
|
||||
var categoryExpanded by remember { mutableStateOf(false) }
|
||||
var frequencyExpanded by remember { mutableStateOf(false) }
|
||||
@@ -195,12 +196,29 @@ fun EditTaskScreen(
|
||||
onClick = {
|
||||
selectedFrequency = frequency
|
||||
frequencyExpanded = false
|
||||
// Clear custom interval if not Custom frequency
|
||||
if (!frequency.name.equals("Custom", ignoreCase = true)) {
|
||||
customIntervalDays = ""
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom Interval Days (only for "Custom" frequency)
|
||||
if (selectedFrequency?.name?.equals("Custom", ignoreCase = true) == true) {
|
||||
OutlinedTextField(
|
||||
value = customIntervalDays,
|
||||
onValueChange = { customIntervalDays = it.filter { char -> char.isDigit() } },
|
||||
label = { Text(stringResource(Res.string.tasks_interval_days)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
supportingText = { Text(stringResource(Res.string.tasks_custom_interval_help)) },
|
||||
singleLine = true
|
||||
)
|
||||
}
|
||||
|
||||
// Priority dropdown
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = priorityExpanded,
|
||||
@@ -292,6 +310,9 @@ fun EditTaskScreen(
|
||||
description = description.ifBlank { null },
|
||||
categoryId = selectedCategory!!.id,
|
||||
frequencyId = selectedFrequency!!.id,
|
||||
customIntervalDays = if (selectedFrequency?.name?.equals("Custom", ignoreCase = true) == true && customIntervalDays.isNotBlank()) {
|
||||
customIntervalDays.toIntOrNull()
|
||||
} else null,
|
||||
priorityId = selectedPriority!!.id,
|
||||
inProgress = inProgress,
|
||||
dueDate = dueDate,
|
||||
|
||||
@@ -5,6 +5,8 @@ import kotlin.concurrent.Volatile
|
||||
|
||||
/**
|
||||
* iOS implementation of TaskCacheManager using NSUserDefaults.
|
||||
* Note: iOS widget dirty flag is handled by native Swift WidgetDataManager
|
||||
* using App Groups shared storage, but we provide these methods for KMM compatibility.
|
||||
*/
|
||||
actual class TaskCacheManager {
|
||||
private val userDefaults = NSUserDefaults.standardUserDefaults
|
||||
@@ -23,8 +25,33 @@ actual class TaskCacheManager {
|
||||
userDefaults.synchronize()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if tasks need refresh due to widget interactions.
|
||||
* Note: iOS primarily uses native Swift WidgetDataManager for this.
|
||||
*/
|
||||
actual fun areTasksDirty(): Boolean {
|
||||
return userDefaults.boolForKey(KEY_DIRTY_FLAG)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark tasks as dirty (needs refresh).
|
||||
*/
|
||||
actual fun markTasksDirty() {
|
||||
userDefaults.setBool(true, KEY_DIRTY_FLAG)
|
||||
userDefaults.synchronize()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the dirty flag after tasks have been refreshed.
|
||||
*/
|
||||
actual fun clearDirtyFlag() {
|
||||
userDefaults.setBool(false, KEY_DIRTY_FLAG)
|
||||
userDefaults.synchronize()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_TASKS = "cached_tasks"
|
||||
private const val KEY_DIRTY_FLAG = "tasks_dirty"
|
||||
|
||||
@Volatile
|
||||
private var instance: TaskCacheManager? = null
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.casera.storage
|
||||
package com.example.casera.storage
|
||||
|
||||
import java.io.File
|
||||
import java.util.prefs.Preferences
|
||||
|
||||
/**
|
||||
* JVM implementation of TaskCacheManager using Java Preferences.
|
||||
* Note: JVM/Desktop doesn't have widgets, so dirty flag methods are no-ops.
|
||||
*/
|
||||
actual class TaskCacheManager {
|
||||
private val prefs = Preferences.userRoot().node(NODE_NAME)
|
||||
@@ -23,9 +23,33 @@ actual class TaskCacheManager {
|
||||
prefs.flush()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if tasks need refresh. JVM doesn't have widgets, always returns false.
|
||||
*/
|
||||
actual fun areTasksDirty(): Boolean {
|
||||
return prefs.getBoolean(KEY_DIRTY_FLAG, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark tasks as dirty. JVM doesn't have widgets but supports the interface.
|
||||
*/
|
||||
actual fun markTasksDirty() {
|
||||
prefs.putBoolean(KEY_DIRTY_FLAG, true)
|
||||
prefs.flush()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the dirty flag.
|
||||
*/
|
||||
actual fun clearDirtyFlag() {
|
||||
prefs.putBoolean(KEY_DIRTY_FLAG, false)
|
||||
prefs.flush()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NODE_NAME = "com.casera.cache"
|
||||
private const val KEY_TASKS = "cached_tasks"
|
||||
private const val KEY_DIRTY_FLAG = "tasks_dirty"
|
||||
|
||||
@Volatile
|
||||
private var instance: TaskCacheManager? = null
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.casera.storage
|
||||
package com.example.casera.storage
|
||||
|
||||
import kotlinx.browser.localStorage
|
||||
|
||||
/**
|
||||
* WASM implementation of TaskCacheManager using browser's localStorage.
|
||||
* Note: Web doesn't have widgets, so dirty flag methods use localStorage for consistency.
|
||||
*/
|
||||
actual class TaskCacheManager {
|
||||
actual fun saveTasks(tasksJson: String) {
|
||||
@@ -18,8 +19,30 @@ actual class TaskCacheManager {
|
||||
localStorage.removeItem(KEY_TASKS)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if tasks need refresh. Web doesn't have widgets, always returns false.
|
||||
*/
|
||||
actual fun areTasksDirty(): Boolean {
|
||||
return localStorage.getItem(KEY_DIRTY_FLAG) == "true"
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark tasks as dirty. Web doesn't have widgets but supports the interface.
|
||||
*/
|
||||
actual fun markTasksDirty() {
|
||||
localStorage.setItem(KEY_DIRTY_FLAG, "true")
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the dirty flag.
|
||||
*/
|
||||
actual fun clearDirtyFlag() {
|
||||
localStorage.setItem(KEY_DIRTY_FLAG, "false")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_TASKS = "cached_tasks"
|
||||
private const val KEY_DIRTY_FLAG = "tasks_dirty"
|
||||
|
||||
private var instance: TaskCacheManager? = null
|
||||
|
||||
|
||||
Reference in New Issue
Block a user