From 99bc730b02235cdf6485bbbc827a07b7d3bebb29 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 03:07:31 +0000 Subject: [PATCH 1/3] Initial plan From 77c764c889321415055adaeba223272ff6c0c8fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 03:12:12 +0000 Subject: [PATCH 2/3] Add last sync time to settings page and markdown file Co-authored-by: yogeshpaliyal <9381846+yogeshpaliyal@users.noreply.github.com> --- .../preference/AppPreferenceDataStore.kt | 13 ++++++++++ .../deepr/sync/SyncRepository.kt | 2 ++ .../deepr/sync/SyncRepositoryImpl.kt | 15 ++++++++++- .../deepr/ui/screens/Settings.kt | 25 +++++++++++++++++++ .../deepr/viewmodel/AccountViewModel.kt | 4 +++ app/src/main/res/values/strings.xml | 3 +++ 6 files changed, 61 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/preference/AppPreferenceDataStore.kt b/app/src/main/java/com/yogeshpaliyal/deepr/preference/AppPreferenceDataStore.kt index 22643a5..b30e61c 100644 --- a/app/src/main/java/com/yogeshpaliyal/deepr/preference/AppPreferenceDataStore.kt +++ b/app/src/main/java/com/yogeshpaliyal/deepr/preference/AppPreferenceDataStore.kt @@ -5,6 +5,7 @@ import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import com.yogeshpaliyal.deepr.viewmodel.SortType @@ -21,6 +22,7 @@ class AppPreferenceDataStore( private val USE_LINK_BASED_ICONS = booleanPreferencesKey("use_link_based_icons") private val SYNC_ENABLED = booleanPreferencesKey("sync_enabled") private val SYNC_FILE_PATH = stringPreferencesKey("sync_file_path") + private val LAST_SYNC_TIME = longPreferencesKey("last_sync_time") } val getSortingOrder: Flow<@SortType String> = @@ -43,6 +45,11 @@ class AppPreferenceDataStore( preferences[SYNC_FILE_PATH] ?: "" // Default to empty path } + val getLastSyncTime: Flow = + context.appDataStore.data.map { preferences -> + preferences[LAST_SYNC_TIME] ?: 0L // Default to 0 (never synced) + } + suspend fun setSortingOrder(order: @SortType String) { context.appDataStore.edit { prefs -> prefs[SORTING_ORDER] = order @@ -66,4 +73,10 @@ class AppPreferenceDataStore( prefs[SYNC_FILE_PATH] = path } } + + suspend fun setLastSyncTime(timestamp: Long) { + context.appDataStore.edit { prefs -> + prefs[LAST_SYNC_TIME] = timestamp + } + } } diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/sync/SyncRepository.kt b/app/src/main/java/com/yogeshpaliyal/deepr/sync/SyncRepository.kt index 827f898..1778e29 100644 --- a/app/src/main/java/com/yogeshpaliyal/deepr/sync/SyncRepository.kt +++ b/app/src/main/java/com/yogeshpaliyal/deepr/sync/SyncRepository.kt @@ -6,4 +6,6 @@ interface SyncRepository { suspend fun syncToMarkdown(): RequestResult suspend fun validateMarkdownFile(filePath: String): RequestResult + + suspend fun recordSyncTime() } diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/sync/SyncRepositoryImpl.kt b/app/src/main/java/com/yogeshpaliyal/deepr/sync/SyncRepositoryImpl.kt index 2faf45c..ab78d2d 100644 --- a/app/src/main/java/com/yogeshpaliyal/deepr/sync/SyncRepositoryImpl.kt +++ b/app/src/main/java/com/yogeshpaliyal/deepr/sync/SyncRepositoryImpl.kt @@ -12,6 +12,9 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import java.io.File import java.io.OutputStream +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale class SyncRepositoryImpl( private val context: Context, @@ -44,6 +47,8 @@ class SyncRepositoryImpl( } writeMarkdownData(it, dataToSync) + // Record sync time on successful completion + recordSyncTime() RequestResult.Success( context.getString( R.string.sync_success, @@ -84,15 +89,23 @@ class SyncRepositoryImpl( } } + override suspend fun recordSyncTime() { + val currentTime = System.currentTimeMillis() + preferenceDataStore.setLastSyncTime(currentTime) + } + private fun writeMarkdownData( file: OutputStream, data: List, ) { file.use { outputStream -> outputStream.bufferedWriter().use { writer -> - // Write header comment + // Write header comment with sync time + val syncTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date()) writer.write("\n") + writer.write("\n") writer.write("# Deeplinks\n\n") + writer.write("**Last Sync:** $syncTime\n\n") writer.write("**Warning:** Please maintain the markdown table format when editing this file.\n\n") // Write markdown table header diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/Settings.kt b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/Settings.kt index 30f7299..7bb4dc6 100644 --- a/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/Settings.kt +++ b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/Settings.kt @@ -54,6 +54,9 @@ import compose.icons.tablericons.Settings import compose.icons.tablericons.Upload import kotlinx.coroutines.flow.collectLatest import org.koin.androidx.compose.koinViewModel +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale data object Settings @@ -81,6 +84,7 @@ fun SettingsScreen( // Collect sync preference states val syncEnabled by viewModel.syncEnabled.collectAsStateWithLifecycle() val syncFilePath by viewModel.syncFilePath.collectAsStateWithLifecycle() + val lastSyncTime by viewModel.lastSyncTime.collectAsStateWithLifecycle() // Launcher for picking sync file location val syncFileLauncher = @@ -245,6 +249,27 @@ fun SettingsScreen( }, ) + // Show last sync time if sync is enabled + ListItem( + headlineContent = { Text(stringResource(R.string.last_sync_time)) }, + supportingContent = { + Text( + if (lastSyncTime > 0) { + val formatter = SimpleDateFormat("MMM dd, yyyy 'at' HH:mm", Locale.getDefault()) + stringResource(R.string.last_sync_time_format, formatter.format(Date(lastSyncTime))) + } else { + stringResource(R.string.last_sync_time_never) + } + ) + }, + leadingContent = { + Icon( + TablerIcons.InfoCircle, + contentDescription = stringResource(R.string.last_sync_time), + ) + }, + ) + if (syncFilePath.isNotEmpty()) { ListItem( modifier = diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/viewmodel/AccountViewModel.kt b/app/src/main/java/com/yogeshpaliyal/deepr/viewmodel/AccountViewModel.kt index 3575502..643df21 100644 --- a/app/src/main/java/com/yogeshpaliyal/deepr/viewmodel/AccountViewModel.kt +++ b/app/src/main/java/com/yogeshpaliyal/deepr/viewmodel/AccountViewModel.kt @@ -265,6 +265,10 @@ class AccountViewModel( preferenceDataStore.getSyncFilePath .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), "") + val lastSyncTime = + preferenceDataStore.getLastSyncTime + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 0L) + fun setSyncEnabled(enabled: Boolean) { viewModelScope.launch(Dispatchers.IO) { preferenceDataStore.setSyncEnabled(enabled) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 72023db..0092104 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -62,6 +62,9 @@ No sync file selected Sync Now Manually sync deeplinks to selected file + Last Sync + Never synced + %s Sync is disabled From 7b02985df805ad0b0cd993876ecdc20e3f65b3b2 Mon Sep 17 00:00:00 2001 From: Yogesh Choudhary Paliyal Date: Mon, 15 Sep 2025 09:31:33 +0530 Subject: [PATCH 3/3] Add URI permission handling for synced markdown files --- .../com/yogeshpaliyal/deepr/sync/SyncRepository.kt | 2 +- .../com/yogeshpaliyal/deepr/ui/screens/Settings.kt | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/sync/SyncRepository.kt b/app/src/main/java/com/yogeshpaliyal/deepr/sync/SyncRepository.kt index 1778e29..b25db0d 100644 --- a/app/src/main/java/com/yogeshpaliyal/deepr/sync/SyncRepository.kt +++ b/app/src/main/java/com/yogeshpaliyal/deepr/sync/SyncRepository.kt @@ -6,6 +6,6 @@ interface SyncRepository { suspend fun syncToMarkdown(): RequestResult suspend fun validateMarkdownFile(filePath: String): RequestResult - + suspend fun recordSyncTime() } diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/Settings.kt b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/Settings.kt index 7bb4dc6..b8a374c 100644 --- a/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/Settings.kt +++ b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/Settings.kt @@ -1,6 +1,7 @@ package com.yogeshpaliyal.deepr.ui.screens import android.Manifest +import android.content.Intent import android.os.Build import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult @@ -92,6 +93,13 @@ fun SettingsScreen( contract = ActivityResultContracts.CreateDocument("text/markdown"), ) { uri -> uri?.let { + val contentResolver = context.contentResolver + + val takeFlags: Int = + Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + // Check for the freshest data. + contentResolver.takePersistableUriPermission(uri, takeFlags) viewModel.setSyncFilePath(it.toString()) } } @@ -259,7 +267,7 @@ fun SettingsScreen( stringResource(R.string.last_sync_time_format, formatter.format(Date(lastSyncTime))) } else { stringResource(R.string.last_sync_time_never) - } + }, ) }, leadingContent = {