diff --git a/app/src/main/java/com/example/util/simpletimetracker/navigation/ActionResolverImpl.kt b/app/src/main/java/com/example/util/simpletimetracker/navigation/ActionResolverImpl.kt
index 4a650bc4..aa0706dd 100644
--- a/app/src/main/java/com/example/util/simpletimetracker/navigation/ActionResolverImpl.kt
+++ b/app/src/main/java/com/example/util/simpletimetracker/navigation/ActionResolverImpl.kt
@@ -77,7 +77,7 @@ class ActionResolverImpl @Inject constructor(
private fun openFile(activity: Activity?, data: OpenFileParams) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE)
- .setType("application/*")
+ .setType(data.type)
if (activity.checkIfIntentResolves(intent)) {
openFileResultLauncher?.launch(intent)
diff --git a/core/src/main/java/com/example/util/simpletimetracker/core/sharedViewModel/BackupViewModel.kt b/core/src/main/java/com/example/util/simpletimetracker/core/sharedViewModel/BackupViewModel.kt
index 0860c4bc..5d92582f 100644
--- a/core/src/main/java/com/example/util/simpletimetracker/core/sharedViewModel/BackupViewModel.kt
+++ b/core/src/main/java/com/example/util/simpletimetracker/core/sharedViewModel/BackupViewModel.kt
@@ -1,6 +1,5 @@
package com.example.util.simpletimetracker.core.sharedViewModel
-import androidx.annotation.StringRes
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
@@ -31,6 +30,7 @@ import com.example.util.simpletimetracker.navigation.params.action.OpenFileParam
import com.example.util.simpletimetracker.navigation.params.notification.SnackBarParams
import com.example.util.simpletimetracker.navigation.params.screen.DataExportSettingDialogParams
import com.example.util.simpletimetracker.navigation.params.screen.DataExportSettingsResult
+import com.example.util.simpletimetracker.navigation.params.screen.HelpDialogParams
import com.example.util.simpletimetracker.navigation.params.screen.StandardDialogParams
import java.text.SimpleDateFormat
import java.util.Date
@@ -157,6 +157,24 @@ class BackupViewModel @Inject constructor(
router.navigate(DataExportSettingDialogParams(CSV_EXPORT_DIALOG_TAG))
}
+ fun onImportCsvClick() {
+ router.navigate(
+ StandardDialogParams(
+ tag = CSV_IMPORT_ALERT_DIALOG_TAG,
+ message = resourceRepo.getString(R.string.archive_deletion_alert),
+ btnPositive = resourceRepo.getString(R.string.ok),
+ btnNegative = resourceRepo.getString(R.string.cancel)
+ )
+ )
+ }
+
+ fun onImportCsvHelpClick() {
+ HelpDialogParams(
+ title = resourceRepo.getString(R.string.settings_import_csv),
+ text = resourceRepo.getString(R.string.settings_import_csv_help),
+ ).let(router::navigate)
+ }
+
fun onExportIcsClick() {
router.navigate(DataExportSettingDialogParams(ICS_EXPORT_DIALOG_TAG))
}
@@ -166,7 +184,18 @@ class BackupViewModel @Inject constructor(
ALERT_DIALOG_TAG -> requestFileWork(
requestCode = RequestCode.REQUEST_CODE_OPEN_FILE,
work = ::onRestoreBackup,
- params = OpenFileParams(::onFileOpenError),
+ params = OpenFileParams(
+ type = FILE_TYPE_BIN_OPEN,
+ notHandledCallback = ::onFileOpenError
+ ),
+ )
+ CSV_IMPORT_ALERT_DIALOG_TAG -> requestFileWork(
+ requestCode = RequestCode.REQUEST_CODE_OPEN_FILE,
+ work = ::onImportCsvFile,
+ params = OpenFileParams(
+ type = FILE_TYPE_CSV_OPEN,
+ notHandledCallback = ::onFileOpenError
+ ),
)
}
}
@@ -236,10 +265,7 @@ class BackupViewModel @Inject constructor(
private fun onSaveBackup(uriString: String?) {
if (uriString == null) return
- executeFileWork(
- successMessageId = R.string.message_backup_saved,
- errorMessageId = R.string.message_save_error,
- ) {
+ executeFileWork {
backupInteractor.saveBackupFile(uriString)
}
}
@@ -312,20 +338,14 @@ class BackupViewModel @Inject constructor(
private fun onRestoreBackup(uriString: String?) {
if (uriString == null) return
- executeFileWork(
- successMessageId = R.string.message_backup_restored,
- errorMessageId = R.string.message_restore_error,
- ) {
+ executeFileWork {
backupInteractor.restoreBackupFile(uriString)
}
}
private fun onSaveCsvFile(uriString: String?) {
if (uriString == null) return
- executeFileWork(
- successMessageId = R.string.message_export_complete,
- errorMessageId = R.string.message_export_error,
- ) {
+ executeFileWork {
csvExportInteractor.saveCsvFile(
uriString = uriString,
range = getRange()
@@ -333,12 +353,16 @@ class BackupViewModel @Inject constructor(
}
}
+ private fun onImportCsvFile(uriString: String?) {
+ if (uriString == null) return
+ executeFileWork {
+ csvExportInteractor.importCsvFile(uriString)
+ }
+ }
+
private fun onSaveIcsFile(uriString: String?) {
if (uriString == null) return
- executeFileWork(
- successMessageId = R.string.message_export_complete,
- errorMessageId = R.string.message_export_error,
- ) {
+ executeFileWork {
icsExportInteractor.saveIcsFile(
uriString = uriString,
range = getRange()
@@ -358,32 +382,26 @@ class BackupViewModel @Inject constructor(
}
private fun executeFileWork(
- @StringRes successMessageId: Int,
- @StringRes errorMessageId: Int,
doWork: suspend () -> ResultCode,
) = viewModelScope.launch {
progressVisibility.set(true)
val resultCode = doWork()
- if (resultCode == ResultCode.SUCCESS) {
- successMessageId
- } else {
- errorMessageId
- }.let(::showMessage)
+ resultCode.message.let(::showMessage)
progressVisibility.set(false)
}
private fun onFileOpenError() {
- showMessage(R.string.settings_file_open_error)
+ showMessage(resourceRepo.getString(R.string.settings_file_open_error))
}
private fun onFileCreateError() {
- showMessage(R.string.settings_file_create_error)
+ showMessage(resourceRepo.getString(R.string.settings_file_create_error))
}
- private fun showMessage(stringResId: Int) {
- val params = SnackBarParams(message = resourceRepo.getString(stringResId))
+ private fun showMessage(string: String) {
+ val params = SnackBarParams(message = string)
router.show(params)
}
@@ -466,9 +484,12 @@ class BackupViewModel @Inject constructor(
private const val CSV_EXPORT_DIALOG_TAG = "csv_export_dialog_tag"
private const val ICS_EXPORT_DIALOG_TAG = "ics_export_dialog_tag"
private const val ALERT_DIALOG_TAG = "alert_dialog_tag"
+ private const val CSV_IMPORT_ALERT_DIALOG_TAG = "csv_import_alert_dialog_tag"
private const val FILE_TYPE_BIN = "application/x-binary"
+ private const val FILE_TYPE_BIN_OPEN = "application/*"
private const val FILE_TYPE_CSV = "text/csv"
+ private const val FILE_TYPE_CSV_OPEN = "text/*"
private const val FILE_TYPE_ICS = "application/ics"
}
}
\ No newline at end of file
diff --git a/core/src/main/java/com/example/util/simpletimetracker/core/utils/TestUtils.kt b/core/src/main/java/com/example/util/simpletimetracker/core/utils/TestUtils.kt
index 913c7d09..dd174b35 100644
--- a/core/src/main/java/com/example/util/simpletimetracker/core/utils/TestUtils.kt
+++ b/core/src/main/java/com/example/util/simpletimetracker/core/utils/TestUtils.kt
@@ -5,10 +5,10 @@ import com.example.util.simpletimetracker.core.mapper.IconImageMapper
import com.example.util.simpletimetracker.domain.extension.orZero
import com.example.util.simpletimetracker.domain.interactor.ActivityFilterInteractor
import com.example.util.simpletimetracker.domain.interactor.CategoryInteractor
+import com.example.util.simpletimetracker.domain.interactor.ClearDataInteractor
import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor
import com.example.util.simpletimetracker.domain.interactor.RecordInteractor
import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor
-import com.example.util.simpletimetracker.domain.interactor.RecordToRecordTagInteractor
import com.example.util.simpletimetracker.domain.interactor.RecordTypeCategoryInteractor
import com.example.util.simpletimetracker.domain.interactor.RecordTypeInteractor
import com.example.util.simpletimetracker.domain.interactor.RunningRecordInteractor
@@ -20,10 +20,9 @@ import com.example.util.simpletimetracker.domain.model.Record
import com.example.util.simpletimetracker.domain.model.RecordTag
import com.example.util.simpletimetracker.domain.model.RecordType
import com.example.util.simpletimetracker.domain.model.RunningRecord
-import com.example.util.simpletimetracker.domain.repo.RunningRecordToRecordTagRepo
-import kotlinx.coroutines.runBlocking
import java.util.concurrent.TimeUnit
import javax.inject.Inject
+import kotlinx.coroutines.runBlocking
class TestUtils @Inject constructor(
private val recordTypeInteractor: RecordTypeInteractor,
@@ -32,23 +31,14 @@ class TestUtils @Inject constructor(
private val categoryInteractor: CategoryInteractor,
private val recordTypeCategoryInteractor: RecordTypeCategoryInteractor,
private val recordTagInteractor: RecordTagInteractor,
- private val recordToRecordTagInteractor: RecordToRecordTagInteractor,
private val activityFilterInteractor: ActivityFilterInteractor,
- private val runningRecordToRecordTagRepo: RunningRecordToRecordTagRepo,
private val prefsInteractor: PrefsInteractor,
private val iconImageMapper: IconImageMapper,
+ private val clearDataInteractor: ClearDataInteractor,
) {
fun clearDatabase() = runBlocking {
- recordTypeInteractor.clear()
- recordInteractor.clear()
- runningRecordInteractor.clear()
- categoryInteractor.clear()
- recordTypeCategoryInteractor.clear()
- recordTagInteractor.clear()
- recordToRecordTagInteractor.clear()
- runningRecordToRecordTagRepo.clear() // TODO what's weird, can't use interactor because of test crash
- activityFilterInteractor.clear()
+ clearDataInteractor.execute()
}
fun clearPrefs() = runBlocking {
diff --git a/core/src/main/res/values-ca/strings.xml b/core/src/main/res/values-ca/strings.xml
index fa975b8a..8e3faadf 100644
--- a/core/src/main/res/values-ca/strings.xml
+++ b/core/src/main/res/values-ca/strings.xml
@@ -291,6 +291,20 @@ Si la configuració \"%s\" és habilitada, l\'aplicació enviarà esdeven
Voleu restaurar la còpia de seguretat?
Exporta a full de càlcul
Exporteu els enregistraments de l\'activitat com un fitxer csv per a veure\'ls al programa de fulls de càlcul preferit.
+ Importació de fulls de càlcul
+ Importa registres d\'activitat des d\'un fitxer csv. Les dades actuals no es perdran.
+
+
+- Nom de l\'activitat entre cometes.
+- Hora d\'inici de l\'enregistrament en el format \"aaaa-MM-dd HH:mm:ss\"
+- Hora de finalització del registre en el format \"aaaa-MM-dd HH:mm:ss\"
+- Comenta entre cometes.
+
+Exemple:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
Exportació ICS
Aquest arxiu no es pot fer servir com a còpia de seguretat.
Hi ha un error en obrir el fitxer
@@ -303,6 +317,9 @@ Si la configuració \"%s\" és habilitada, l\'aplicació enviarà esdeven
No es troba l\'aplicació
Exportació complerta
Error d\'exportació
+ Importació completada
+ Registres afegits: %s
+ Error d\'importació
Hi ha hagut un error amb la còpia de seguretat automàtica.
Hi ha hagut un error amb l\'exportació automàtica.
Proveu de seleccionar una ubicació diferent
diff --git a/core/src/main/res/values-de/strings.xml b/core/src/main/res/values-de/strings.xml
index 1dd0263a..c8f0cdcc 100644
--- a/core/src/main/res/values-de/strings.xml
+++ b/core/src/main/res/values-de/strings.xml
@@ -291,6 +291,20 @@ Wenn die Einstellung \"%s\" aktiviert ist, sendet die App Ereignisse zu g
Backup wiederherstellen?
Tabellenkalkulation exportieren
Exportiere die Aktivitätsdaten als csv-Datei, um sie in einer bevorzugten Tabellenkalkulationsanwendung anzuzeigen.
+ Tabellenimport
+ Aktivitätsdatensätze aus einer CSV-Datei importieren. Aktuelle Daten gehen nicht verloren.
+
+
+- Aktivitätsname in Anführungszeichen.
+– Startzeit der Aufzeichnung im Format „jjjj-MM-tt HH:mm:ss“
+– Endzeit der Aufzeichnung im Format „jjjj-MM-tt HH:mm:ss“
+- Comment in quotes.
+
+Beispiel:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
ICS-Export
Diese Datei kann nicht als Backup verwendet werden.
Fehler beim Öffnen der Datei
@@ -303,6 +317,9 @@ Wenn die Einstellung \"%s\" aktiviert ist, sendet die App Ereignisse zu g
App nicht gefunden
Export beendet
Exportfehler
+ Import abgeschlossen
+ Hinzugefügte Datensätze: %s
+ Importfehler
Bei der automatischen Sicherung ist ein Fehler aufgetreten.
Beim automatischen Export ist ein Fehler aufgetreten.
Versuchen Sie, einen anderen Ort auszuwählen
diff --git a/core/src/main/res/values-es/strings.xml b/core/src/main/res/values-es/strings.xml
index 4f87cfd4..c0a25762 100644
--- a/core/src/main/res/values-es/strings.xml
+++ b/core/src/main/res/values-es/strings.xml
@@ -291,6 +291,20 @@ Si la configuración \"%s\" está habilitada, la aplicación enviará eve
¿Desea restaurar la copia de seguridad?
Exportar hoja de cálculo
Exportar registros como un archivo csv para visualizar los datos en su programa de hojas de cálculo preferido.
+ Importación de hoja de cálculo
+ Importar registros de actividad desde un archivo csv. Los datos actuales no se perderán.
+
+
+- Nombre de la actividad entre comillas.
+- Hora de inicio del registro en el formato \"yyyy-MM-dd HH:mm:ss\"
+- Hora de finalización del registro en el formato \"yyyy-MM-dd HH:mm:ss\"
+- Comentar entre comillas.
+
+Ejemplo:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
Exportación ICS
Este archivo no se puede utilizar como copia de seguridad.
Error abriendo el archivo
@@ -303,6 +317,9 @@ Si la configuración \"%s\" está habilitada, la aplicación enviará eve
No se encontró la apliación
Exportación completada
Error de exportación
+ Importación completada
+ Registros agregados: %s
+ Error de importación
Hubo un error con la copia de seguridad automática.
Hubo un error con la exportación automática.
Intenta seleccionar una ubicación diferente
diff --git a/core/src/main/res/values-fr/strings.xml b/core/src/main/res/values-fr/strings.xml
index 08eb319e..e0546909 100644
--- a/core/src/main/res/values-fr/strings.xml
+++ b/core/src/main/res/values-fr/strings.xml
@@ -291,6 +291,20 @@ Si le paramètre \"%s\" est activé, l\'application enverra des événeme
Restaurer la sauvegarde ?
Exporter dans une feuille de calcul
Exportez les enregistrements d\'activité sous la forme de fichier CSV pour les afficher dans le tableur de votre choix.
+ Importation de feuille de calcul
+ Importez des enregistrements d\'activité à partir d\'un fichier csv. Les données actuelles ne seront pas perdues.
+
+
+- Nom de l\'activité entre guillemets.
+- Heure de début de l\'enregistrement au format \"aaaa-MM-jj HH:mm:ss\"
+- Heure de fin d\'enregistrement au format \"aaaa-MM-jj HH:mm:ss\"
+- Commentaire entre guillemets.
+
+Exemple:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
Exportation ICS
Ce fichier ne peut pas être utilisé comme sauvegarde.
Erreur lors de l\'ouverture du fichier
@@ -303,6 +317,9 @@ Si le paramètre \"%s\" est activé, l\'application enverra des événeme
Application non trouvée
Exportation terminée
Erreur d\'exportation
+ Importation terminée
+ Enregistrements ajoutés: %s
+ Erreur d\'importation
Une erreur s\'est produite lors de la sauvegarde automatique.
Une erreur s\'est produite lors de l\'exportation automatique.
Essayez de sélectionner un emplacement différent
diff --git a/core/src/main/res/values-hi/strings.xml b/core/src/main/res/values-hi/strings.xml
index 3631d2ce..97ba4e61 100644
--- a/core/src/main/res/values-hi/strings.xml
+++ b/core/src/main/res/values-hi/strings.xml
@@ -291,6 +291,20 @@
पुनर्स्थापित करे?
स्प्रेडशीट निर्यात
एक पसंदीदा स्प्रेडशीट एप्लिकेशन में देखने के लिए CSV फ़ाइल के रूप में निर्यात गतिविधि रिकॉर्ड।
+ स्प्रेडशीट आयात
+ CSV फ़ाइल से गतिविधि रिकॉर्ड आयात करें। वर्तमान डेटा नष्ट नहीं होगा।
+
+
+- गतिविधि का नाम उद्धरणों में।
+- रिकॉर्ड का प्रारंभ समय \"yyyy-MM-dd HH:mm:ss\"
प्रारूप में
+- रिकॉर्ड का समाप्ति समय \"yyyy-MM-dd HH:mm:ss\"
प्रारूप में
+- उद्धरणों में टिप्पणी करें।
+
+उदाहरण:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
ICS निर्यात
इस फ़ाइल का उपयोग बैकअप के रूप में नहीं किया जा सकता है।
त्रुटि खोलने की फ़ाइल
@@ -303,6 +317,9 @@
आवेदन नहीं मिला
निर्यात पूरा किया गया
निर्यात त्रुटि
+ आयात पूरा हुआ
+ रिकॉर्ड जोड़े गए: %s
+ आयात त्रुटि
स्वचालित बैकअप के साथ एक त्रुटि हुई थी।
स्वचालित निर्यात में त्रुटि हुई थी।
अलग स्थान का चयन करने का प्रयास करें
diff --git a/core/src/main/res/values-in/strings.xml b/core/src/main/res/values-in/strings.xml
index dd6be0ab..8adc1132 100644
--- a/core/src/main/res/values-in/strings.xml
+++ b/core/src/main/res/values-in/strings.xml
@@ -291,6 +291,20 @@ Jika setelan \"%s\" diaktifkan, aplikasi akan mengirimkan peristiwa pada
Pulihkan cadangan?
Ekspor lembar kerja
Ekspor rekor-rekor kegiatan sebagai sebuah berkas csv untuk melihatnya dalam aplikasi spreadsheet yang diinginkan.
+ Impor spreadsheet
+ Impor rekaman aktivitas dari file csv. Data saat ini tidak akan hilang.
+
+
+- Nama aktivitas dalam tanda kutip.
+- Waktu mulai pencatatan dalam format \"yyyy-MM-dd HH:mm:ss\"
+- Waktu akhir pencatatan dalam format \"yyyy-MM-dd HH:mm:ss\"
+- Beri komentar dalam tanda kutip.
+
+Contoh:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
Ekspor ICS
File ini tidak dapat digunakan sebagai cadangan.
Galat membuka berkas
@@ -303,6 +317,9 @@ Jika setelan \"%s\" diaktifkan, aplikasi akan mengirimkan peristiwa pada
Aplikasi tidak ditemukan
Ekspor selesai
Kesalahan ekspor
+ Impor selesai
+ Catatan ditambahkan: %s
+ Kesalahan impor
Terjadi kesalahan dengan pencadangan otomatis.
Terjadi kesalahan dengan ekspor otomatis.
Cobalah untuk memilih lokasi yang berbeda
diff --git a/core/src/main/res/values-it/strings.xml b/core/src/main/res/values-it/strings.xml
index 7a03874a..072429b9 100644
--- a/core/src/main/res/values-it/strings.xml
+++ b/core/src/main/res/values-it/strings.xml
@@ -291,6 +291,20 @@ Se l\'impostazione \"%s\" è abilitata, l\'app invierà eventi sull\'atti
Ripristina backup?
Esporta foglio di calcolo
Esporta le registrazioni dell\'attività come file csv da visualizzare con l\'applicazione predefinita per i fogli di calcolo.
+ Importazione da foglio di calcolo
+ Importa i record delle attività da un file csv. I dati correnti non andranno persi.
+
+
+- Nome dell\'attività tra virgolette.
+- Ora di inizio della registrazione nel formato \"aaaa-MM-gg HH:mm:ss\"
+- Ora di fine registrazione nel formato \"aaaa-MM-gg HH:mm:ss\"
+- Commento tra virgolette.
+
+Esempio:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
Esportazione ICS
Questo file non può essere utilizzato come backup.
Errore nell\'apertura del file
@@ -303,6 +317,9 @@ Se l\'impostazione \"%s\" è abilitata, l\'app invierà eventi sull\'atti
Applicazione non trovata
Esportazione completata
Errore di esportazione
+ Importazione completata
+ Record aggiunti: %s
+ Errore di importazione
Si è verificato un errore con il backup automatico.
Si è verificato un errore con l\'esportazione automatica.
Prova a selezionare una posizione diversa
diff --git a/core/src/main/res/values-ja/strings.xml b/core/src/main/res/values-ja/strings.xml
index 18eb2fbb..e2b34dee 100644
--- a/core/src/main/res/values-ja/strings.xml
+++ b/core/src/main/res/values-ja/strings.xml
@@ -291,6 +291,20 @@
バックアップを復元しますか?
スプレッドシートのエクスポート
アクティビティ記録をcsvファイルとしてエクスポートし、お好みのスプレッドシートアプリケーションで表示することができます。
+ スプレッドシートのインポート
+ CSV ファイルからアクティビティ記録をインポートします。現在のデータは失われません。
+
+
+- 引用符で囲んだアクティビティ名。
+- 「yyyy-MM-dd HH:mm:ss」形式の記録開始時刻
+- 「yyyy-MM-dd HH:mm:ss」形式の記録の終了時刻
+- 引用符で囲んだコメント。
+
+例:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
ICS エクスポート
このファイルはバックアップとして使用できません。
ファイルを開く際にエラーが発生しました
@@ -303,6 +317,9 @@
アプリケーションが見つかりません
エクスポート完了
エクスポートエラー
+ インポートが完了しました
+ 追加されたレコード: %s
+ インポートエラー
自動バックアップでエラーが発生しました。
自動エクスポートでエラーが発生しました。
別の場所を選択してみてください
diff --git a/core/src/main/res/values-nl/strings.xml b/core/src/main/res/values-nl/strings.xml
index 86d82d3c..80dc85ca 100644
--- a/core/src/main/res/values-nl/strings.xml
+++ b/core/src/main/res/values-nl/strings.xml
@@ -291,6 +291,20 @@ Als de instelling \"%s\" is ingeschakeld, stuurt de app gebeurtenissen ov
Back-up terugzetten?
Spreadsheet exporteren
Exporteer activiteitenrecords als een csv-bestand om ze te bekijken in een voorkeurspreadsheet-applicatie.
+ Spreadsheets importeren
+ Activiteitenrecords importeren vanuit een csv-bestand. Huidige gegevens gaan niet verloren.
+
+
+- Activiteitsnaam tussen aanhalingstekens.
+- Starttijd van opname in het formaat \"jjjj-MM-dd UU:mm:ss\"
+- Eindtijd van opname in het formaat \"jjjj-MM-dd UU:mm:ss\"
+- Reageer tussen aanhalingstekens.
+
+Voorbeeld:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
ICS-export
Dit bestand kan niet als back-up worden gebruikt.
Fout bij openen bestand
@@ -303,6 +317,9 @@ Als de instelling \"%s\" is ingeschakeld, stuurt de app gebeurtenissen ov
Applicatie niet gevonden
Export voltooid
Exportfout
+ Importeren voltooid
+ Records toegevoegd: %s
+ Fout importeren
Er is een fout opgetreden bij de automatische back-up.
Er is een fout opgetreden bij het automatisch exporteren.
Probeer een andere locatie te selecteren
diff --git a/core/src/main/res/values-pt/strings.xml b/core/src/main/res/values-pt/strings.xml
index 458e1726..9f882b33 100644
--- a/core/src/main/res/values-pt/strings.xml
+++ b/core/src/main/res/values-pt/strings.xml
@@ -291,6 +291,20 @@ Se a configuração \"%s\" estiver habilitada, o aplicativo enviará even
Restaurar backup?
Exportação de planilha
Exporte registros de atividades como um arquivo csv para visualizar no aplicativo de planilhas de sua preferência.
+ Importação de planilha
+ Importe registros de atividades de um arquivo csv. Os dados atuais não serão perdidos.
+
+
+- Nome da atividade entre aspas.
+- Hora de início da gravação no formato \"aaaa-MM-dd HH:mm:ss\"
+- Hora de término do registro no formato \"aaaa-MM-dd HH:mm:ss\"
+- Comente entre aspas.
+
+Exemplo:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
Exportação de ICS
Este arquivo não pode ser usado como backup.
Erro ao abrir o arquivo
@@ -303,6 +317,9 @@ Se a configuração \"%s\" estiver habilitada, o aplicativo enviará even
Aplicação não encontrada
Exportação concluída
Erro de exportação
+ Importação concluída
+ Registros adicionados: %s
+ Erro de importação
Ocorreu um erro com o backup automático.
Ocorreu um erro com a exportação automática.
Tente selecionar um local diferente
diff --git a/core/src/main/res/values-ru/strings.xml b/core/src/main/res/values-ru/strings.xml
index 2be2f732..d9a8d1ce 100644
--- a/core/src/main/res/values-ru/strings.xml
+++ b/core/src/main/res/values-ru/strings.xml
@@ -291,6 +291,20 @@
Восстановить из резервной копии?
Экспорт таблицы
Экспортируйте записи в виде файла CSV для просмотра в предпочтительном приложении для работы с электронными таблицами.
+ Импорт таблицы
+ Импорт записей об активности из CSV-файла. Текущие данные не будут потеряны.
+
+
+- Название действия в кавычках.
+- Время начала записи в формате \"гггг-ММ-дд ЧЧ:мм:сс\"
+- Время окончания записи в формате \"гггг-ММ-дд ЧЧ:мм:сс\"
+- Комментарий в кавычках.
+
+Пример:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
ICS экспорт
Этот файл не может быть использован в качестве резервной копии.
Ошибка при открытии файла
@@ -303,6 +317,9 @@
Приложение не найдено
Экспорт завершен
Ошибка экспорта
+ Импорт завершен
+ Добавлено записей: %s
+ Ошибка импорта
Произошла ошибка с автоматическим резервным копированием.
Произошла ошибка при автоматическом экспорте.
Попробуйте выбрать другое место
diff --git a/core/src/main/res/values-sv/strings.xml b/core/src/main/res/values-sv/strings.xml
index 44df8bb9..3e0dcabf 100644
--- a/core/src/main/res/values-sv/strings.xml
+++ b/core/src/main/res/values-sv/strings.xml
@@ -291,6 +291,20 @@ Om inställningen \"%s\" är aktiverad, kommer appen att skicka händelse
Återställa säkerhetskopia?
Exportera kalkyblad
Exportera händelser som en csv-fil för att öppna med kalkylbladsprogram.
+ Importera kalkylark
+ Importera aktivitetsposter från en csv-fil. Aktuell data kommer inte att gå förlorad.
+
+
+- Aktivitetsnamn inom citattecken.
+- Starttid för registrering i formatet \"åååå-MM-dd HH:mm:ss\"
+- Sluttid för posten i formatet \"åååå-MM-dd HH:mm:ss\"
+- Kommentera inom citattecken.
+
+Exempel:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
ICS export
Den här filen kan inte användas som säkerhetskopia.
Fel vid öppnandet av fil
@@ -303,6 +317,9 @@ Om inställningen \"%s\" är aktiverad, kommer appen att skicka händelse
Applikation ej hittad
Export slutförd
Exportfel
+ Importen slutförd
+ Poster tillagda: %s
+ Importfel
Det uppstod ett fel med automatisk säkerhetskopiering.
Det uppstod ett fel med automatisk export.
Försök att välja en annan plats
diff --git a/core/src/main/res/values-tr/strings.xml b/core/src/main/res/values-tr/strings.xml
index 0958b906..4e58de67 100644
--- a/core/src/main/res/values-tr/strings.xml
+++ b/core/src/main/res/values-tr/strings.xml
@@ -291,6 +291,20 @@ Ek olarak:
Yedeklemeyi geri yükle?
Çizelge dışa aktar
Aktivite kayıtlarını, tercih edilen çizelge uygulamasında görüntülemek için csv dosyası olarak dışa aktar.
+ E-tablo içe aktarma
+ Etkinlik kayıtlarını bir csv dosyasından içe aktarın. Mevcut veriler kaybolmaz.
+
+
+- Tırnak içinde etkinlik adı.
+- \"yyyy-AA-gg SS:dd:ss\"
biçimindeki kaydın başlangıç zamanı
+- \"yyyy-AA-gg SS:dd:ss\"
biçimindeki kaydın bitiş zamanı
+- Tırnak içinde yorum yapın.
+
+Örnek:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
ICS dışa aktarma
Bu dosya yedek olarak kullanılamaz.
Dosyayı açarken hata oluştu
@@ -303,6 +317,9 @@ Ek olarak:
Uygulama bulunumadı
Dışa aktarma tamamlandı
Dışa aktarma hatası
+ İçe aktarma tamamlandı
+ Eklenen kayıtlar: %s
+ Alma hatası
Otomatik yedeklemede bir hata oluştu.
Otomatik dışa aktarmayla ilgili bir hata oluştu.
Farklı bir konum seçmeyi deneyin
diff --git a/core/src/main/res/values-uk/strings.xml b/core/src/main/res/values-uk/strings.xml
index 53e66fda..5fe302b4 100644
--- a/core/src/main/res/values-uk/strings.xml
+++ b/core/src/main/res/values-uk/strings.xml
@@ -291,6 +291,20 @@
Відновити резервну копію?
Експорт електронних таблиць
Експортуйте записи діяльності як файл CSV для перегляду у бажаній програмі для перегляду електронних таблиць.
+ Імпорт таблиці
+ Імпортуйте записи про дії з файлу CSV. Поточні дані не будуть втрачені.
+
+
+- Назва діяльності в лапках.
+- Час початку запису у форматі \"рррр-ММ-дд ГГ:хх:сс\"
+- Час завершення запису у форматі \"рррр-ММ-дд ГГ:хх:сс\"
+- Коментар у лапках.
+
+Приклад:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
ICS експорт
Цей файл не можна використовувати як резервну копію.
Помилка відкриття файлу
@@ -303,6 +317,9 @@
Додаток не знайдено
Експорт завершено
Помилка експорту
+ Імпорт завершено
+ Додано записів: %s
+ Помилка імпорту
Під час автоматичного резервного копіювання сталася помилка.
Під час автоматичного експорту сталася помилка.
Спробуйте вибрати інше місце
diff --git a/core/src/main/res/values-zh-rTW/strings.xml b/core/src/main/res/values-zh-rTW/strings.xml
index 07d893b9..b7abb313 100644
--- a/core/src/main/res/values-zh-rTW/strings.xml
+++ b/core/src/main/res/values-zh-rTW/strings.xml
@@ -291,6 +291,20 @@
還原備份?
匯出工作簿
將活動記錄導出為 csv 文件,以便在首選電子表格應用程序中查看。
+ 電子表格導入
+ 從 csv 文件導入活動記錄。當前數據不會丟失。
+
+
+- 引號中的活動名稱。
+- 記錄開始時間格式為\"yyyy-MM-dd HH:mm:ss\"
+- 記錄結束時間格式為\"yyyy-MM-dd HH:mm:ss\"
+- 評論用引號。
+
+示例:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
匯出 ICS
此檔案不能用在備份上
開啟檔案錯誤
@@ -303,6 +317,9 @@
應用未找到
已匯出成功
匯出錯誤
+ 導入完成
+ 添加的記錄:%s
+ 導入錯誤
自動備份出錯。
自動導出時出錯。
嘗試選擇不同的位置
diff --git a/core/src/main/res/values-zh/strings.xml b/core/src/main/res/values-zh/strings.xml
index 69be334f..818b2650 100644
--- a/core/src/main/res/values-zh/strings.xml
+++ b/core/src/main/res/values-zh/strings.xml
@@ -291,6 +291,20 @@
恢复备份?
导出为电子表格
将活动记录导出为csv文件以在电子表格应用程序中查看。
+ 电子表格导入
+ 从 csv 文件导入活动记录。当前数据不会丢失。
+
+
+- 引号中的活动名称。
+- 记录开始时间格式为\"yyyy-MM-dd HH:mm:ss\"
+- 记录结束时间格式为\"yyyy-MM-dd HH:mm:ss\"
+- 评论用引号。
+
+示例:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
ICS 出口
此文件不能用作备份。
打开文件时出错
@@ -303,6 +317,9 @@
找不到应用程序
导出已完成
导出错误
+ 导入完成
+ 添加的记录:%s
+ 导入错误
自动备份出错。
自动导出时出错。
尝试选择不同的位置
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index 2a446f65..eb7f77b6 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -291,6 +291,20 @@ If \"%s\" setting is enabled, app will send events on activity started or
Restore backup?
Spreadsheet export
Export activity records as a csv file to view in a preferred spreadsheet application.
+ Spreadsheet import
+ Import activity records from a csv file. Current data will not be lost.
+
+
+- Activity name in quotes.
+- Start time of record in the format \"yyyy-MM-dd HH:mm:ss\"
+- End time of record in the format \"yyyy-MM-dd HH:mm:ss\"
+- Comment in quotes.
+
+Example:
+
+\"Reading\",2023-05-30 14:45:21,2023-05-30 15:45:21,\"Comment, comment\"
+ ]]>
ICS export
This file cannot be used as a backup.
Error opening file
@@ -303,6 +317,9 @@ If \"%s\" setting is enabled, app will send events on activity started or
Application not found
Export completed
Export error
+ Import completed
+ Records added: %s
+ Import error
There was an error with automatic backup.
There was an error with automatic export.
Try to select different location
diff --git a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/BackupRepoImpl.kt b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/BackupRepoImpl.kt
index f562ebc3..91cdfc25 100644
--- a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/BackupRepoImpl.kt
+++ b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/BackupRepoImpl.kt
@@ -3,7 +3,10 @@ package com.example.util.simpletimetracker.data_local.resolver
import android.content.ContentResolver
import android.net.Uri
import android.os.ParcelFileDescriptor
+import com.example.util.simpletimetracker.core.R
+import com.example.util.simpletimetracker.core.repo.ResourceRepo
import com.example.util.simpletimetracker.domain.extension.orZero
+import com.example.util.simpletimetracker.domain.interactor.ClearDataInteractor
import com.example.util.simpletimetracker.domain.model.ActivityFilter
import com.example.util.simpletimetracker.domain.model.AppColor
import com.example.util.simpletimetracker.domain.model.Category
@@ -19,13 +22,8 @@ import com.example.util.simpletimetracker.domain.repo.RecordTagRepo
import com.example.util.simpletimetracker.domain.repo.RecordToRecordTagRepo
import com.example.util.simpletimetracker.domain.repo.RecordTypeCategoryRepo
import com.example.util.simpletimetracker.domain.repo.RecordTypeRepo
-import com.example.util.simpletimetracker.domain.repo.RunningRecordRepo
-import com.example.util.simpletimetracker.domain.repo.RunningRecordToRecordTagRepo
import com.example.util.simpletimetracker.domain.resolver.BackupRepo
import com.example.util.simpletimetracker.domain.resolver.ResultCode
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import timber.log.Timber
import java.io.BufferedReader
import java.io.FileOutputStream
import java.io.IOException
@@ -33,6 +31,9 @@ import java.io.InputStream
import java.io.InputStreamReader
import javax.inject.Inject
import javax.inject.Singleton
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import timber.log.Timber
/**
* Do not change backup parts order, always add new to the end.
@@ -48,161 +49,156 @@ class BackupRepoImpl @Inject constructor(
private val recordToRecordTagRepo: RecordToRecordTagRepo,
private val recordTagRepo: RecordTagRepo,
private val activityFilterRepo: ActivityFilterRepo,
- private val runningRecordRepo: RunningRecordRepo,
- private val runningRecordToRecordTagRepo: RunningRecordToRecordTagRepo,
+ private val clearDataInteractor: ClearDataInteractor,
+ private val resourceRepo: ResourceRepo,
) : BackupRepo {
- override suspend fun saveBackupFile(uriString: String): ResultCode =
- withContext(Dispatchers.IO) {
- var fileDescriptor: ParcelFileDescriptor? = null
- var fileOutputStream: FileOutputStream? = null
+ override suspend fun saveBackupFile(
+ uriString: String
+ ): ResultCode = withContext(Dispatchers.IO) {
+ var fileDescriptor: ParcelFileDescriptor? = null
+ var fileOutputStream: FileOutputStream? = null
+
+ try {
+ val uri = Uri.parse(uriString)
+ fileDescriptor = contentResolver.openFileDescriptor(uri, "wt")
+ fileOutputStream = fileDescriptor?.fileDescriptor?.let(::FileOutputStream)
+
+ // Write file identification
+ val identificationBackupRow: String = BACKUP_IDENTIFICATION + "\n"
+ fileOutputStream?.write(identificationBackupRow.toByteArray())
+
+ // Write data
+ recordTypeRepo.getAll().forEach {
+ fileOutputStream?.write(
+ it.let(::toBackupString).toByteArray()
+ )
+ }
+ recordRepo.getAll().forEach {
+ fileOutputStream?.write(
+ it.let(::toBackupString).toByteArray()
+ )
+ }
+ categoryRepo.getAll().forEach {
+ fileOutputStream?.write(
+ it.let(::toBackupString).toByteArray()
+ )
+ }
+ recordTypeCategoryRepo.getAll().forEach {
+ fileOutputStream?.write(
+ it.let(::toBackupString).toByteArray()
+ )
+ }
+ recordTagRepo.getAll().forEach {
+ fileOutputStream?.write(
+ it.let(::toBackupString).toByteArray()
+ )
+ }
+ recordToRecordTagRepo.getAll().forEach {
+ fileOutputStream?.write(
+ it.let(::toBackupString).toByteArray()
+ )
+ }
+ activityFilterRepo.getAll().forEach {
+ fileOutputStream?.write(
+ it.let(::toBackupString).toByteArray()
+ )
+ }
+ fileOutputStream?.close()
+ fileDescriptor?.close()
+ ResultCode.Success(resourceRepo.getString(R.string.message_backup_saved))
+ } catch (e: Exception) {
+ Timber.e(e)
+ ResultCode.Error(resourceRepo.getString(R.string.message_save_error))
+ } finally {
try {
- val uri = Uri.parse(uriString)
- fileDescriptor = contentResolver.openFileDescriptor(uri, "wt")
- fileOutputStream = fileDescriptor?.fileDescriptor?.let(::FileOutputStream)
-
- // Write file identification
- val identificationBackupRow: String = BACKUP_IDENTIFICATION + "\n"
- fileOutputStream?.write(identificationBackupRow.toByteArray())
-
- // Write data
- recordTypeRepo.getAll().forEach {
- fileOutputStream?.write(
- it.let(::toBackupString).toByteArray()
- )
- }
- recordRepo.getAll().forEach {
- fileOutputStream?.write(
- it.let(::toBackupString).toByteArray()
- )
- }
- categoryRepo.getAll().forEach {
- fileOutputStream?.write(
- it.let(::toBackupString).toByteArray()
- )
- }
- recordTypeCategoryRepo.getAll().forEach {
- fileOutputStream?.write(
- it.let(::toBackupString).toByteArray()
- )
- }
- recordTagRepo.getAll().forEach {
- fileOutputStream?.write(
- it.let(::toBackupString).toByteArray()
- )
- }
- recordToRecordTagRepo.getAll().forEach {
- fileOutputStream?.write(
- it.let(::toBackupString).toByteArray()
- )
- }
- activityFilterRepo.getAll().forEach {
- fileOutputStream?.write(
- it.let(::toBackupString).toByteArray()
- )
- }
-
fileOutputStream?.close()
fileDescriptor?.close()
- ResultCode.SUCCESS
- } catch (e: Exception) {
- Timber.e(e)
- ResultCode.ERROR
- } finally {
- try {
- fileOutputStream?.close()
- fileDescriptor?.close()
- } catch (e: IOException) {
- // Do nothing
- }
+ } catch (e: IOException) {
+ // Do nothing
}
}
+ }
- override suspend fun restoreBackupFile(uriString: String): ResultCode =
- withContext(Dispatchers.IO) {
- var inputStream: InputStream? = null
- var reader: BufferedReader? = null
-
- try {
- val uri = Uri.parse(uriString)
- inputStream = contentResolver.openInputStream(uri)
- reader = inputStream?.let(::InputStreamReader)?.let(::BufferedReader)
-
- var line: String
- var parts: List
-
- // Check file identification
- line = reader?.readLine().orEmpty()
- if (line != BACKUP_IDENTIFICATION) return@withContext ResultCode.ERROR
-
- recordTypeRepo.clear()
- recordRepo.clear()
- categoryRepo.clear()
- recordTypeCategoryRepo.clear()
- recordTagRepo.clear()
- recordToRecordTagRepo.clear()
- activityFilterRepo.clear()
- runningRecordRepo.clear()
- runningRecordToRecordTagRepo.clear()
-
- // Read data
- while (reader?.readLine()?.also { line = it } != null) {
- parts = line.split("\t")
- when (parts[0]) {
- ROW_RECORD_TYPE -> {
- recordTypeFromBackupString(parts).let {
- recordTypeRepo.add(it)
- }
+ override suspend fun restoreBackupFile(
+ uriString: String
+ ): ResultCode = withContext(Dispatchers.IO) {
+ var inputStream: InputStream? = null
+ var reader: BufferedReader? = null
+ val errorCode = ResultCode.Error(resourceRepo.getString(R.string.message_restore_error))
+
+ try {
+ val uri = Uri.parse(uriString)
+ inputStream = contentResolver.openInputStream(uri)
+ reader = inputStream?.let(::InputStreamReader)?.let(::BufferedReader)
+
+ var line: String
+ var parts: List
+
+ // Check file identification
+ line = reader?.readLine().orEmpty()
+ if (line != BACKUP_IDENTIFICATION) return@withContext errorCode
+
+ clearDataInteractor.execute()
+
+ // Read data
+ while (reader?.readLine()?.also { line = it } != null) {
+ parts = line.split("\t")
+ when (parts[0]) {
+ ROW_RECORD_TYPE -> {
+ recordTypeFromBackupString(parts).let {
+ recordTypeRepo.add(it)
}
- ROW_RECORD -> {
- recordFromBackupString(parts).let { (record, recordToRecordTag) ->
- recordRepo.add(record)
- if (recordToRecordTag != null) {
- recordToRecordTagRepo.add(recordToRecordTag)
- }
+ }
+ ROW_RECORD -> {
+ recordFromBackupString(parts).let { (record, recordToRecordTag) ->
+ recordRepo.add(record)
+ if (recordToRecordTag != null) {
+ recordToRecordTagRepo.add(recordToRecordTag)
}
}
- ROW_CATEGORY -> {
- categoryFromBackupString(parts).let {
- categoryRepo.add(it)
- }
+ }
+ ROW_CATEGORY -> {
+ categoryFromBackupString(parts).let {
+ categoryRepo.add(it)
}
- ROW_TYPE_CATEGORY -> {
- typeCategoryFromBackupString(parts).let {
- recordTypeCategoryRepo.add(it)
- }
+ }
+ ROW_TYPE_CATEGORY -> {
+ typeCategoryFromBackupString(parts).let {
+ recordTypeCategoryRepo.add(it)
}
- ROW_RECORD_TAG -> {
- recordTagFromBackupString(parts).let {
- recordTagRepo.add(it)
- }
+ }
+ ROW_RECORD_TAG -> {
+ recordTagFromBackupString(parts).let {
+ recordTagRepo.add(it)
}
- ROW_RECORD_TO_RECORD_TAG -> {
- recordToRecordTagFromBackupString(parts).let {
- recordToRecordTagRepo.add(it)
- }
+ }
+ ROW_RECORD_TO_RECORD_TAG -> {
+ recordToRecordTagFromBackupString(parts).let {
+ recordToRecordTagRepo.add(it)
}
- ROW_ACTIVITY_FILTER -> {
- activityFilterFromBackupString(parts).let {
- activityFilterRepo.add(it)
- }
+ }
+ ROW_ACTIVITY_FILTER -> {
+ activityFilterFromBackupString(parts).let {
+ activityFilterRepo.add(it)
}
}
}
- ResultCode.SUCCESS
- } catch (e: Exception) {
- Timber.e(e)
- ResultCode.ERROR
- } finally {
- try {
- inputStream?.close()
- reader?.close()
- } catch (e: IOException) {
- // Do nothing
- }
+ }
+ ResultCode.Success(resourceRepo.getString(R.string.message_backup_restored))
+ } catch (e: Exception) {
+ Timber.e(e)
+ errorCode
+ } finally {
+ try {
+ inputStream?.close()
+ reader?.close()
+ } catch (e: IOException) {
+ // Do nothing
}
}
+ }
private fun toBackupString(recordType: RecordType): String {
return String.format(
diff --git a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/CsvRepoImpl.kt b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/CsvRepoImpl.kt
index a8b8102c..e6373a1a 100644
--- a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/CsvRepoImpl.kt
+++ b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/CsvRepoImpl.kt
@@ -3,6 +3,10 @@ package com.example.util.simpletimetracker.data_local.resolver
import android.content.ContentResolver
import android.net.Uri
import android.os.ParcelFileDescriptor
+import com.example.util.simpletimetracker.core.R
+import com.example.util.simpletimetracker.core.mapper.ColorMapper
+import com.example.util.simpletimetracker.core.repo.ResourceRepo
+import com.example.util.simpletimetracker.domain.model.AppColor
import com.example.util.simpletimetracker.domain.model.Category
import com.example.util.simpletimetracker.domain.model.Range
import com.example.util.simpletimetracker.domain.model.Record
@@ -14,15 +18,18 @@ import com.example.util.simpletimetracker.domain.repo.RecordTypeCategoryRepo
import com.example.util.simpletimetracker.domain.repo.RecordTypeRepo
import com.example.util.simpletimetracker.domain.resolver.CsvRepo
import com.example.util.simpletimetracker.domain.resolver.ResultCode
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import timber.log.Timber
+import java.io.BufferedReader
import java.io.FileOutputStream
import java.io.IOException
+import java.io.InputStream
+import java.io.InputStreamReader
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
import javax.inject.Inject
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import timber.log.Timber
class CsvRepoImpl @Inject constructor(
private val contentResolver: ContentResolver,
@@ -30,6 +37,7 @@ class CsvRepoImpl @Inject constructor(
private val recordRepo: RecordRepo,
private val recordTypeCategoryRepo: RecordTypeCategoryRepo,
private val recordTagRepo: RecordTagRepo,
+ private val resourceRepo: ResourceRepo,
) : CsvRepo {
override suspend fun saveCsvFile(
@@ -74,10 +82,10 @@ class CsvRepoImpl @Inject constructor(
fileOutputStream?.close()
fileDescriptor?.close()
- ResultCode.SUCCESS
+ ResultCode.Success(resourceRepo.getString(R.string.message_export_complete))
} catch (e: Exception) {
Timber.e(e)
- ResultCode.ERROR
+ ResultCode.Error(resourceRepo.getString(R.string.message_export_error))
} finally {
try {
fileOutputStream?.close()
@@ -88,6 +96,90 @@ class CsvRepoImpl @Inject constructor(
}
}
+ override suspend fun importCsvFile(
+ uriString: String
+ ): ResultCode = withContext(Dispatchers.IO) {
+ var inputStream: InputStream? = null
+ var reader: BufferedReader? = null
+
+ try {
+ val uri = Uri.parse(uriString)
+ inputStream = contentResolver.openInputStream(uri)
+ reader = inputStream?.let(::InputStreamReader)?.let(::BufferedReader)
+
+ var line = ""
+ var addedRecords = 0L
+ val currentTypes = recordTypeRepo.getAll()
+ val newAddedTypes = mutableListOf()
+
+ // Read data
+ while (reader?.readLine()?.also { line = it } != null) {
+ line = line.removePrefix("\"")
+ val typeName = line.substringBefore(delimiter = "\"", missingDelimiterValue = "")
+ line = line.removePrefix("$typeName\",")
+
+ val timeStartedString = line.substringBefore(delimiter = ",", missingDelimiterValue = "")
+ val timeStarted = parseDateTime(timeStartedString)
+ line = line.removePrefix("$timeStartedString,")
+
+ val timeEndedString = line.substringBefore(delimiter = ",", missingDelimiterValue = "")
+ val timeEnded = parseDateTime(timeEndedString)
+ line = line.removePrefix("$timeEndedString,")
+
+ line = line.removePrefix("\"")
+ val comment = line.substringBefore(delimiter = "\"", missingDelimiterValue = "")
+
+ if (
+ typeName.isNotEmpty() &&
+ timeStarted != null &&
+ timeEnded != null
+ ) {
+ val typeId: Long = currentTypes.firstOrNull { it.name == typeName }?.id
+ ?: newAddedTypes.firstOrNull { it.name == typeName }?.id
+ ?: run {
+ val newType = RecordType(
+ name = typeName,
+ icon = "",
+ color = AppColor(
+ colorId = (0..ColorMapper.colorsNumber).random(),
+ colorInt = "",
+ ),
+ hidden = false,
+ goalTime = 0,
+ dailyGoalTime = 0,
+ weeklyGoalTime = 0,
+ monthlyGoalTime = 0,
+ )
+ val newTypeId = recordTypeRepo.add(newType)
+ newType.copy(id = newTypeId).let(newAddedTypes::add)
+ newTypeId
+ }
+ val record = Record(
+ typeId = typeId,
+ timeStarted = timeStarted,
+ timeEnded = timeEnded,
+ comment = comment,
+ )
+ recordRepo.add(record)
+ addedRecords++
+ }
+ }
+ val messageText = resourceRepo.getString(R.string.message_import_complete)
+ val messageHint = resourceRepo.getString(R.string.message_import_complete_hint, addedRecords)
+ ResultCode.Success("$messageText\n$messageHint")
+ } catch (e: Exception) {
+ Timber.e(e)
+ ResultCode.Error(resourceRepo.getString(R.string.message_import_error))
+ } finally {
+ try {
+ inputStream?.close()
+ reader?.close()
+ } catch (e: IOException) {
+ // Do nothing
+ }
+ }
+ }
+
private fun toCsvString(
record: Record,
recordType: RecordType?,
@@ -136,6 +228,14 @@ class CsvRepoImpl @Inject constructor(
return "$hr:$min:$sec"
}
+ private fun parseDateTime(timeString: String): Long? {
+ return synchronized(dateTimeFormat) {
+ runCatching {
+ dateTimeFormat.parse(timeString)
+ }.getOrNull()?.time
+ }
+ }
+
companion object {
private const val CSV_HEADER = "activity name,time started,time ended,comment,categories,record tags,duration,duration minutes\n"
diff --git a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/IcsRepoImpl.kt b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/IcsRepoImpl.kt
index da8efefa..53707147 100644
--- a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/IcsRepoImpl.kt
+++ b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/IcsRepoImpl.kt
@@ -91,10 +91,10 @@ class IcsRepoImpl @Inject constructor(
fileOutputStream?.close()
fileDescriptor?.close()
- ResultCode.SUCCESS
+ ResultCode.Success(resourceRepo.getString(R.string.message_export_complete))
} catch (e: Exception) {
Timber.e(e)
- ResultCode.ERROR
+ ResultCode.Error(resourceRepo.getString(R.string.message_export_error))
} finally {
try {
fileOutputStream?.close()
diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/ActivityFilterInteractor.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/ActivityFilterInteractor.kt
index 28ad2dcb..3fc2c60d 100644
--- a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/ActivityFilterInteractor.kt
+++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/ActivityFilterInteractor.kt
@@ -29,8 +29,4 @@ class ActivityFilterInteractor @Inject constructor(
suspend fun remove(id: Long) {
activityFilterRepo.remove(id)
}
-
- suspend fun clear() {
- activityFilterRepo.clear()
- }
}
\ No newline at end of file
diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/CategoryInteractor.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/CategoryInteractor.kt
index 626d9e06..5a23651d 100644
--- a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/CategoryInteractor.kt
+++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/CategoryInteractor.kt
@@ -28,8 +28,4 @@ class CategoryInteractor @Inject constructor(
categoryRepo.remove(id)
recordTypeCategoryRepo.removeAll(id)
}
-
- suspend fun clear() {
- categoryRepo.clear()
- }
}
\ No newline at end of file
diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/ClearDataInteractor.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/ClearDataInteractor.kt
new file mode 100644
index 00000000..1214473f
--- /dev/null
+++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/ClearDataInteractor.kt
@@ -0,0 +1,37 @@
+package com.example.util.simpletimetracker.domain.interactor
+
+import com.example.util.simpletimetracker.domain.repo.ActivityFilterRepo
+import com.example.util.simpletimetracker.domain.repo.CategoryRepo
+import com.example.util.simpletimetracker.domain.repo.RecordRepo
+import com.example.util.simpletimetracker.domain.repo.RecordTagRepo
+import com.example.util.simpletimetracker.domain.repo.RecordToRecordTagRepo
+import com.example.util.simpletimetracker.domain.repo.RecordTypeCategoryRepo
+import com.example.util.simpletimetracker.domain.repo.RecordTypeRepo
+import com.example.util.simpletimetracker.domain.repo.RunningRecordRepo
+import com.example.util.simpletimetracker.domain.repo.RunningRecordToRecordTagRepo
+import javax.inject.Inject
+
+class ClearDataInteractor @Inject constructor(
+ private val recordTypeRepo: RecordTypeRepo,
+ private val recordRepo: RecordRepo,
+ private val categoryRepo: CategoryRepo,
+ private val recordTypeCategoryRepo: RecordTypeCategoryRepo,
+ private val recordToRecordTagRepo: RecordToRecordTagRepo,
+ private val recordTagRepo: RecordTagRepo,
+ private val activityFilterRepo: ActivityFilterRepo,
+ private val runningRecordRepo: RunningRecordRepo,
+ private val runningRecordToRecordTagRepo: RunningRecordToRecordTagRepo,
+) {
+
+ suspend fun execute() {
+ recordTypeRepo.clear()
+ recordRepo.clear()
+ categoryRepo.clear()
+ recordTypeCategoryRepo.clear()
+ recordTagRepo.clear()
+ recordToRecordTagRepo.clear()
+ activityFilterRepo.clear()
+ runningRecordRepo.clear()
+ runningRecordToRecordTagRepo.clear()
+ }
+}
\ No newline at end of file
diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/CsvExportInteractor.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/CsvExportInteractor.kt
index f6ad752d..0c0ff3b0 100644
--- a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/CsvExportInteractor.kt
+++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/CsvExportInteractor.kt
@@ -12,4 +12,8 @@ class CsvExportInteractor @Inject constructor(
suspend fun saveCsvFile(uriString: String, range: Range?): ResultCode {
return csvRepo.saveCsvFile(uriString = uriString, range = range)
}
+
+ suspend fun importCsvFile(uriString: String): ResultCode {
+ return csvRepo.importCsvFile(uriString)
+ }
}
\ No newline at end of file
diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordInteractor.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordInteractor.kt
index ac0d1972..f8bc299c 100644
--- a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordInteractor.kt
+++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordInteractor.kt
@@ -81,8 +81,4 @@ class RecordInteractor @Inject constructor(
recordToRecordTagRepo.removeAllByRecordId(id)
recordRepo.remove(id)
}
-
- suspend fun clear() {
- recordRepo.clear()
- }
}
\ No newline at end of file
diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordTagInteractor.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordTagInteractor.kt
index f7c8a8a7..694d6cf0 100644
--- a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordTagInteractor.kt
+++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordTagInteractor.kt
@@ -67,10 +67,6 @@ class RecordTagInteractor @Inject constructor(
repo.removeByType(typeId)
}
- suspend fun clear() {
- repo.clear()
- }
-
// TODO remove sort and sort when needed.
private fun sort(items: List): List {
return items.sortedBy { it.name.lowercase(Locale.getDefault()) }
diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordToRecordTagInteractor.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordToRecordTagInteractor.kt
index 7538e510..97555511 100644
--- a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordToRecordTagInteractor.kt
+++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordToRecordTagInteractor.kt
@@ -10,8 +10,4 @@ class RecordToRecordTagInteractor @Inject constructor(
suspend fun getRecordIdsByTagId(tagId: Long): List {
return repo.getRecordIdsByTagId(tagId)
}
-
- suspend fun clear() {
- repo.clear()
- }
}
\ No newline at end of file
diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordTypeCategoryInteractor.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordTypeCategoryInteractor.kt
index 44d15f84..8ca8d457 100644
--- a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordTypeCategoryInteractor.kt
+++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordTypeCategoryInteractor.kt
@@ -39,8 +39,4 @@ class RecordTypeCategoryInteractor @Inject constructor(
suspend fun removeAllByType(typeId: Long) {
recordTypeCategoryRepo.removeAllByType(typeId)
}
-
- suspend fun clear() {
- recordTypeCategoryRepo.clear()
- }
}
\ No newline at end of file
diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordTypeInteractor.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordTypeInteractor.kt
index 24626f04..12ac7d01 100644
--- a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordTypeInteractor.kt
+++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordTypeInteractor.kt
@@ -67,10 +67,6 @@ class RecordTypeInteractor @Inject constructor(
recordTypeRepo.remove(id)
}
- suspend fun clear() {
- recordTypeRepo.clear()
- }
-
private suspend fun sort(
cardOrder: CardOrder?,
records: List,
diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RunningRecordInteractor.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RunningRecordInteractor.kt
index 9b480b3b..bbc0ec3b 100644
--- a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RunningRecordInteractor.kt
+++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RunningRecordInteractor.kt
@@ -28,8 +28,4 @@ class RunningRecordInteractor @Inject constructor(
runningRecordToRecordTagRepo.removeAllByRunningRecordId(id)
runningRecordRepo.remove(id)
}
-
- suspend fun clear() {
- runningRecordRepo.clear()
- }
}
\ No newline at end of file
diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RunningRecordToRecordTagInteractor.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RunningRecordToRecordTagInteractor.kt
deleted file mode 100644
index 815d0003..00000000
--- a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RunningRecordToRecordTagInteractor.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.example.util.simpletimetracker.domain.interactor
-
-import com.example.util.simpletimetracker.domain.repo.RunningRecordToRecordTagRepo
-import javax.inject.Inject
-
-class RunningRecordToRecordTagInteractor @Inject constructor(
- private val repo: RunningRecordToRecordTagRepo,
-) {
-
- suspend fun clear() {
- repo.clear()
- }
-}
\ No newline at end of file
diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/resolver/CsvRepo.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/resolver/CsvRepo.kt
index 32215075..b1940798 100644
--- a/domain/src/main/java/com/example/util/simpletimetracker/domain/resolver/CsvRepo.kt
+++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/resolver/CsvRepo.kt
@@ -8,4 +8,8 @@ interface CsvRepo {
uriString: String,
range: Range?,
): ResultCode
+
+ suspend fun importCsvFile(
+ uriString: String,
+ ): ResultCode
}
\ No newline at end of file
diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/resolver/ResultCode.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/resolver/ResultCode.kt
index 8edd50ea..0c16fc0e 100644
--- a/domain/src/main/java/com/example/util/simpletimetracker/domain/resolver/ResultCode.kt
+++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/resolver/ResultCode.kt
@@ -1,5 +1,8 @@
package com.example.util.simpletimetracker.domain.resolver
-enum class ResultCode {
- SUCCESS, ERROR
+sealed interface ResultCode {
+ val message: String
+
+ data class Success(override val message: String) : ResultCode
+ data class Error(override val message: String) : ResultCode
}
\ No newline at end of file
diff --git a/features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/automaticBackup/interactor/AutomaticBackupInteractorImpl.kt b/features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/automaticBackup/interactor/AutomaticBackupInteractorImpl.kt
index df7fb5f5..df4094cd 100644
--- a/features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/automaticBackup/interactor/AutomaticBackupInteractorImpl.kt
+++ b/features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/automaticBackup/interactor/AutomaticBackupInteractorImpl.kt
@@ -42,7 +42,7 @@ class AutomaticBackupInteractorImpl @Inject constructor(
}
val result = backupInteractor.saveBackupFile(uri)
- if (result == ResultCode.SUCCESS) {
+ if (result is ResultCode.Success) {
schedule()
prefsInteractor.setAutomaticBackupLastSaveTime(System.currentTimeMillis())
} else {
diff --git a/features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/automaticExport/interactor/AutomaticExportInteractorImpl.kt b/features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/automaticExport/interactor/AutomaticExportInteractorImpl.kt
index 672990a2..4afbb8df 100644
--- a/features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/automaticExport/interactor/AutomaticExportInteractorImpl.kt
+++ b/features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/automaticExport/interactor/AutomaticExportInteractorImpl.kt
@@ -42,7 +42,7 @@ class AutomaticExportInteractorImpl @Inject constructor(
}
val result = csvExportInteractor.saveCsvFile(uri, range = null)
- if (result == ResultCode.SUCCESS) {
+ if (result is ResultCode.Success) {
schedule()
prefsInteractor.setAutomaticExportLastSaveTime(System.currentTimeMillis())
} else {
diff --git a/features/feature_settings/src/main/java/com/example/util/simpletimetracker/feature_settings/view/SettingsFragment.kt b/features/feature_settings/src/main/java/com/example/util/simpletimetracker/feature_settings/view/SettingsFragment.kt
index ab0e1dd1..7c7d5e57 100644
--- a/features/feature_settings/src/main/java/com/example/util/simpletimetracker/feature_settings/view/SettingsFragment.kt
+++ b/features/feature_settings/src/main/java/com/example/util/simpletimetracker/feature_settings/view/SettingsFragment.kt
@@ -126,6 +126,8 @@ class SettingsFragment :
layoutSettingsSaveBackup.setOnClick(backupViewModel::onSaveClick)
layoutSettingsRestoreBackup.setOnClick(backupViewModel::onRestoreClick)
layoutSettingsExportCsv.setOnClick(backupViewModel::onExportCsvClick)
+ layoutSettingsImportCsv.setOnClick(backupViewModel::onImportCsvClick)
+ btnSettingsImportCsvHelp.setOnClick(backupViewModel::onImportCsvHelpClick)
layoutSettingsExportIcs.setOnClick(backupViewModel::onExportIcsClick)
checkboxSettingsAutomaticBackup.setOnClick(backupViewModel::onAutomaticBackupClick)
checkboxSettingsAutomaticExport.setOnClick(backupViewModel::onAutomaticExportClick)
diff --git a/features/feature_settings/src/main/res/layout/settings_backup_layout.xml b/features/feature_settings/src/main/res/layout/settings_backup_layout.xml
index 1fa76dfa..42fba64f 100644
--- a/features/feature_settings/src/main/res/layout/settings_backup_layout.xml
+++ b/features/feature_settings/src/main/res/layout/settings_backup_layout.xml
@@ -220,6 +220,76 @@
android:layout_width="match_parent"
android:layout_marginTop="12dp" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Unit),
) : ActionParams
\ No newline at end of file