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..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,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..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 @@ -54,6 +55,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 +85,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 = @@ -88,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()) } } @@ -245,6 +257,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