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 5b1cf2213..2d1ee4fd1 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 @@ -68,6 +68,7 @@ class TestUtils @Inject constructor( goals: List = emptyList(), archived: Boolean = false, instant: Boolean = false, + instantDuration: Long = 0, categories: List = emptyList(), ) = runBlocking { val icons = iconImageMapper @@ -89,6 +90,7 @@ class TestUtils @Inject constructor( icon = iconId, hidden = archived, instant = instant, + instantDuration = instantDuration, ) val typeId = recordTypeInteractor.add(data) diff --git a/data_local/schemas/com.example.util.simpletimetracker.data_local.database.AppDatabase/21.json b/data_local/schemas/com.example.util.simpletimetracker.data_local.database.AppDatabase/21.json new file mode 100644 index 000000000..d29acea77 --- /dev/null +++ b/data_local/schemas/com.example.util.simpletimetracker.data_local.database.AppDatabase/21.json @@ -0,0 +1,619 @@ +{ + "formatVersion": 1, + "database": { + "version": 21, + "identityHash": "6d9ae8e03df9c2df6235163b62e65437", + "entities": [ + { + "tableName": "records", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type_id` INTEGER NOT NULL, `time_started` INTEGER NOT NULL, `time_ended` INTEGER NOT NULL, `comment` TEXT NOT NULL, `tag_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "typeId", + "columnName": "type_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeStarted", + "columnName": "time_started", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeEnded", + "columnName": "time_ended", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tagId", + "columnName": "tag_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recordTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon` TEXT NOT NULL, `color` INTEGER NOT NULL, `color_int` TEXT NOT NULL, `hidden` INTEGER NOT NULL, `instant` INTEGER NOT NULL, `instantDuration` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "colorInt", + "columnName": "color_int", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "instant", + "columnName": "instant", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "instantDuration", + "columnName": "instantDuration", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "runningRecords", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `time_started` INTEGER NOT NULL, `comment` TEXT NOT NULL, `tag_id` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeStarted", + "columnName": "time_started", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tagId", + "columnName": "tag_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "categories", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `color` INTEGER NOT NULL, `color_int` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "colorInt", + "columnName": "color_int", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recordTypeCategory", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record_type_id` INTEGER NOT NULL, `category_id` INTEGER NOT NULL, PRIMARY KEY(`record_type_id`, `category_id`))", + "fields": [ + { + "fieldPath": "recordTypeId", + "columnName": "record_type_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "record_type_id", + "category_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recordTags", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `icon` TEXT NOT NULL, `color` INTEGER NOT NULL, `color_int` TEXT NOT NULL, `icon_color_source` INTEGER NOT NULL, `archived` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "typeId", + "columnName": "type_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "colorInt", + "columnName": "color_int", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconColorSource", + "columnName": "icon_color_source", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "archived", + "columnName": "archived", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recordToRecordTag", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record_id` INTEGER NOT NULL, `record_tag_id` INTEGER NOT NULL, PRIMARY KEY(`record_id`, `record_tag_id`))", + "fields": [ + { + "fieldPath": "recordId", + "columnName": "record_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recordTagId", + "columnName": "record_tag_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "record_id", + "record_tag_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "runningRecordToRecordTag", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`running_record_id` INTEGER NOT NULL, `record_tag_id` INTEGER NOT NULL, PRIMARY KEY(`running_record_id`, `record_tag_id`))", + "fields": [ + { + "fieldPath": "runningRecordId", + "columnName": "running_record_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recordTagId", + "columnName": "record_tag_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "running_record_id", + "record_tag_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recordTypeToTag", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record_type_id` INTEGER NOT NULL, `record_tag_id` INTEGER NOT NULL, PRIMARY KEY(`record_type_id`, `record_tag_id`))", + "fields": [ + { + "fieldPath": "recordTypeId", + "columnName": "record_type_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tagId", + "columnName": "record_tag_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "record_type_id", + "record_tag_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recordTypeToDefaultTag", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record_type_id` INTEGER NOT NULL, `record_tag_id` INTEGER NOT NULL, PRIMARY KEY(`record_type_id`, `record_tag_id`))", + "fields": [ + { + "fieldPath": "recordTypeId", + "columnName": "record_type_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tagId", + "columnName": "record_tag_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "record_type_id", + "record_tag_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "activityFilters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `selectedIds` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `color` INTEGER NOT NULL, `color_int` TEXT NOT NULL, `selected` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "selectedIds", + "columnName": "selectedIds", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "colorInt", + "columnName": "color_int", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "selected", + "columnName": "selected", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "favouriteComments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `comment` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recordTypeGoals", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type_id` INTEGER NOT NULL, `range` INTEGER NOT NULL, `type` INTEGER NOT NULL, `value` INTEGER NOT NULL, `category_id` INTEGER NOT NULL, `days_of_week` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "typeId", + "columnName": "type_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "range", + "columnName": "range", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "daysOfWeek", + "columnName": "days_of_week", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "favouriteIcons", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `icon` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "complexRules", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `disabled` INTEGER NOT NULL, `actionType` INTEGER NOT NULL, `actionSetTagIds` TEXT NOT NULL, `conditionStartingTypeIds` TEXT NOT NULL, `conditionCurrentTypeIds` TEXT NOT NULL, `conditionDaysOfWeek` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "disabled", + "columnName": "disabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "action", + "columnName": "actionType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "actionSetTagIds", + "columnName": "actionSetTagIds", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conditionStartingTypeIds", + "columnName": "conditionStartingTypeIds", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conditionCurrentTypeIds", + "columnName": "conditionCurrentTypeIds", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conditionDaysOfWeek", + "columnName": "conditionDaysOfWeek", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6d9ae8e03df9c2df6235163b62e65437')" + ] + } +} \ No newline at end of file diff --git a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/database/AppDatabase.kt b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/database/AppDatabase.kt index 5c79e9db2..ff78da61a 100644 --- a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/database/AppDatabase.kt +++ b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/database/AppDatabase.kt @@ -36,7 +36,7 @@ import com.example.util.simpletimetracker.data_local.model.RunningRecordToRecord FavouriteIconDBO::class, ComplexRuleDBO::class, ], - version = 20, + version = 21, exportSchema = true, ) abstract class AppDatabase : RoomDatabase() { diff --git a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/database/AppDatabaseMigrations.kt b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/database/AppDatabaseMigrations.kt index 680405794..4ab4dd33c 100644 --- a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/database/AppDatabaseMigrations.kt +++ b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/database/AppDatabaseMigrations.kt @@ -27,6 +27,7 @@ class AppDatabaseMigrations { migration_17_18, migration_18_19, migration_19_20, + migration_20_21, ) private val migration_1_2 = object : Migration(1, 2) { @@ -264,5 +265,13 @@ class AppDatabaseMigrations { ) } } + + private val migration_20_21 = object : Migration(20, 21) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "ALTER TABLE recordTypes ADD COLUMN instantDuration INTEGER NOT NULL DEFAULT 0", + ) + } + } } } \ No newline at end of file diff --git a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/mapper/RecordTypeDataLocalMapper.kt b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/mapper/RecordTypeDataLocalMapper.kt index b4ae7b681..3544cf2b7 100644 --- a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/mapper/RecordTypeDataLocalMapper.kt +++ b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/mapper/RecordTypeDataLocalMapper.kt @@ -17,6 +17,7 @@ class RecordTypeDataLocalMapper @Inject constructor() { colorInt = dbo.colorInt, ), instant = dbo.instant, + instantDuration = dbo.instantDuration, hidden = dbo.hidden, ) } @@ -29,6 +30,7 @@ class RecordTypeDataLocalMapper @Inject constructor() { color = domain.color.colorId, colorInt = domain.color.colorInt, instant = domain.instant, + instantDuration = domain.instantDuration, hidden = domain.hidden, ) } diff --git a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/model/RecordTypeDBO.kt b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/model/RecordTypeDBO.kt index bc4c19b91..2c54d1f3b 100644 --- a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/model/RecordTypeDBO.kt +++ b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/model/RecordTypeDBO.kt @@ -28,4 +28,8 @@ data class RecordTypeDBO( @ColumnInfo(name = "instant") val instant: Boolean, + + // Seconds. + @ColumnInfo(name = "instantDuration") + val instantDuration: Long, ) \ No newline at end of file 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 72998a8f5..02b43be10 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 @@ -286,7 +286,7 @@ class BackupRepoImpl @Inject constructor( private fun toBackupString(recordType: RecordType): String { return String.format( - "$ROW_RECORD_TYPE\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", + "$ROW_RECORD_TYPE\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", recordType.id.toString(), recordType.name.clean(), recordType.icon, @@ -298,6 +298,7 @@ class BackupRepoImpl @Inject constructor( "", // weekly goal time is moved to separate table "", // monthly goal time is moved to separate table, (if (recordType.instant) 1 else 0).toString(), + recordType.instantDuration, ) } @@ -511,6 +512,7 @@ class BackupRepoImpl @Inject constructor( // parts[9] - weekly time is moved to separate table // parts[10] - monthly time is moved to separate table, instant = parts.getOrNull(11)?.toIntOrNull() == 1, + instantDuration = parts.getOrNull(12)?.toLongOrNull().orZero() ) to goalTimes } 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 28f8e71a3..ba74484f9 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 @@ -151,6 +151,7 @@ class CsvRepoImpl @Inject constructor( ), hidden = false, instant = false, + instantDuration = 0L, ) val newTypeId = recordTypeRepo.add(newType) newType.copy(id = newTypeId).let(newAddedTypes::add) diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/AddRunningRecordMediator.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/AddRunningRecordMediator.kt index 2cd09195d..4514e94d1 100644 --- a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/AddRunningRecordMediator.kt +++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/AddRunningRecordMediator.kt @@ -1,6 +1,8 @@ package com.example.util.simpletimetracker.domain.interactor import com.example.util.simpletimetracker.domain.model.Range +import com.example.util.simpletimetracker.domain.model.Record +import com.example.util.simpletimetracker.domain.model.RecordType import com.example.util.simpletimetracker.domain.model.ResultContainer import com.example.util.simpletimetracker.domain.model.RunningRecord import javax.inject.Inject @@ -10,6 +12,8 @@ class AddRunningRecordMediator @Inject constructor( private val removeRunningRecordMediator: RemoveRunningRecordMediator, private val recordInteractor: RecordInteractor, private val runningRecordInteractor: RunningRecordInteractor, + private val recordTypeInteractor: RecordTypeInteractor, + private val addRecordMediator: AddRecordMediator, private val recordTypeToDefaultTagInteractor: RecordTypeToDefaultTagInteractor, private val notificationTypeInteractor: NotificationTypeInteractor, private val notificationInactivityInteractor: NotificationInactivityInteractor, @@ -67,42 +71,84 @@ class AddRunningRecordMediator @Inject constructor( tagIds = actualTags, comment = comment, ) - add( - typeId = typeId, - comment = comment, - tagIds = actualTags, - timeStarted = timeStarted ?: System.currentTimeMillis(), + addInternal( + StartParams( + typeId = typeId, + comment = comment, + tagIds = actualTags, + timeStarted = timeStarted ?: System.currentTimeMillis(), + ), ) // Show goal count only on timer start, otherwise it would show on change also. notificationGoalCountInteractor.checkAndShow(typeId) pomodoroStartInteractor.checkAndStart(typeId) } - suspend fun add( + // Used separately only for changing running activity, + // due to some poor (probably) decisions id of running record is it's type id, + // so if type is changed - need to remove old and add new data. + suspend fun addAfterChange( typeId: Long, timeStarted: Long, comment: String, tagIds: List, ) { - if (runningRecordInteractor.get(typeId) == null && typeId > 0L) { - RunningRecord( - id = typeId, + addInternal( + StartParams( + typeId = typeId, timeStarted = timeStarted, comment = comment, tagIds = tagIds, + ), + ) + } + + private suspend fun addInternal(params: StartParams) { + val type = recordTypeInteractor.get(params.typeId) ?: return + if (type.instant) { + addInstantRecord(params, type) + } else { + addRunningRecord(params) + } + } + + private suspend fun addRunningRecord( + params: StartParams, + ) { + if (runningRecordInteractor.get(params.typeId) == null && params.typeId > 0L) { + RunningRecord( + id = params.typeId, + timeStarted = params.timeStarted, + comment = params.comment, + tagIds = params.tagIds, ).let { runningRecordInteractor.add(it) - notificationTypeInteractor.checkAndShow(typeId) + notificationTypeInteractor.checkAndShow(params.typeId) notificationInactivityInteractor.cancel() // Schedule only on first activity start. if (runningRecordInteractor.getAll().size == 1) notificationActivityInteractor.checkAndSchedule() - notificationGoalTimeInteractor.checkAndReschedule(listOf(typeId)) + notificationGoalTimeInteractor.checkAndReschedule(listOf(params.typeId)) widgetInteractor.updateWidgets() wearInteractor.update() } } } + private suspend fun addInstantRecord( + params: StartParams, + type: RecordType, + ) { + Record( + typeId = params.typeId, + timeStarted = params.timeStarted, + timeEnded = params.timeStarted + type.instantDuration * 1000, + comment = params.comment, + tagIds = params.tagIds + ).let { + addRecordMediator.add(it) + } + } + private suspend fun processRules( typeId: Long, timeStarted: Long, @@ -163,4 +209,11 @@ class AddRunningRecordMediator @Inject constructor( val defaultTags = recordTypeToDefaultTagInteractor.getTags(typeId) return (tagIds + defaultTags + tagIdsFromRules).toSet().toList() } + + private data class StartParams( + val typeId: Long, + val timeStarted: Long, + val comment: String, + val tagIds: List, + ) } \ No newline at end of file diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/model/RecordType.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/model/RecordType.kt index 3bf12c788..b7a84f607 100644 --- a/domain/src/main/java/com/example/util/simpletimetracker/domain/model/RecordType.kt +++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/model/RecordType.kt @@ -6,5 +6,6 @@ data class RecordType( val icon: String, val color: AppColor, val instant: Boolean, + val instantDuration: Long, val hidden: Boolean = false, ) \ No newline at end of file diff --git a/features/feature_change_category/src/main/java/com/example/util/simpletimetracker/feature_change_category/view/ChangeCategoryFragment.kt b/features/feature_change_category/src/main/java/com/example/util/simpletimetracker/feature_change_category/view/ChangeCategoryFragment.kt index a63f518a5..7fcbb280e 100644 --- a/features/feature_change_category/src/main/java/com/example/util/simpletimetracker/feature_change_category/view/ChangeCategoryFragment.kt +++ b/features/feature_change_category/src/main/java/com/example/util/simpletimetracker/feature_change_category/view/ChangeCategoryFragment.kt @@ -174,7 +174,7 @@ class ChangeCategoryFragment : } override fun onDurationSet(durationSeconds: Long, tag: String?) { - viewModel.onDurationSet( + viewModel.onGoalDurationSet( tag = tag, duration = durationSeconds, anchor = binding.btnChangeCategorySave, diff --git a/features/feature_change_goals/api/src/main/java/com/example/util/simpletimetracker/feature_change_goals/api/GoalsViewModelDelegate.kt b/features/feature_change_goals/api/src/main/java/com/example/util/simpletimetracker/feature_change_goals/api/GoalsViewModelDelegate.kt index 66f7fdc40..951358c1d 100644 --- a/features/feature_change_goals/api/src/main/java/com/example/util/simpletimetracker/feature_change_goals/api/GoalsViewModelDelegate.kt +++ b/features/feature_change_goals/api/src/main/java/com/example/util/simpletimetracker/feature_change_goals/api/GoalsViewModelDelegate.kt @@ -11,7 +11,7 @@ interface GoalsViewModelDelegate { suspend fun initialize(id: RecordTypeGoal.IdData) fun onGoalsVisible() fun onNotificationsHintClick() - fun onDurationSet(tag: String?, duration: Long, anchor: Any) + fun onGoalDurationSet(tag: String?, duration: Long, anchor: Any) fun onDurationDisabled(tag: String?) fun onGoalTypeSelected(range: RecordTypeGoal.Range, position: Int) fun onGoalCountChange(range: RecordTypeGoal.Range, count: String) diff --git a/features/feature_change_goals/src/main/java/com/example/util/simpletimetracker/feature_change_goals/delegate/GoalsViewModelDelegateImpl.kt b/features/feature_change_goals/src/main/java/com/example/util/simpletimetracker/feature_change_goals/delegate/GoalsViewModelDelegateImpl.kt index fcaa314e5..317709322 100644 --- a/features/feature_change_goals/src/main/java/com/example/util/simpletimetracker/feature_change_goals/delegate/GoalsViewModelDelegateImpl.kt +++ b/features/feature_change_goals/src/main/java/com/example/util/simpletimetracker/feature_change_goals/delegate/GoalsViewModelDelegateImpl.kt @@ -49,12 +49,14 @@ class GoalsViewModelDelegateImpl @Inject constructor( router.execute(OpenSystemSettings.Notifications) } - override fun onDurationSet(tag: String?, duration: Long, anchor: Any) { + override fun onGoalDurationSet(tag: String?, duration: Long, anchor: Any) { + if (tag !in tags) return onNewGoalDuration(tag, duration) checkExactAlarmPermissionInteractor.execute(anchor) } override fun onDurationDisabled(tag: String?) { + if (tag !in tags) return onNewGoalDuration(tag, 0) } @@ -257,5 +259,12 @@ class GoalsViewModelDelegateImpl @Inject constructor( private const val DAILY_GOAL_TIME_DIALOG_TAG = "daily_goal_time_dialog_tag" private const val WEEKLY_GOAL_TIME_DIALOG_TAG = "weekly_goal_time_dialog_tag" private const val MONTHLY_GOAL_TIME_DIALOG_TAG = "monthly_goal_time_dialog_tag" + + private val tags = listOf( + SESSION_GOAL_TIME_DIALOG_TAG, + DAILY_GOAL_TIME_DIALOG_TAG, + WEEKLY_GOAL_TIME_DIALOG_TAG, + MONTHLY_GOAL_TIME_DIALOG_TAG, + ) } } \ No newline at end of file diff --git a/features/feature_change_record_type/src/main/java/com/example/util/simpletimetracker/feature_change_record_type/view/ChangeRecordTypeFragment.kt b/features/feature_change_record_type/src/main/java/com/example/util/simpletimetracker/feature_change_record_type/view/ChangeRecordTypeFragment.kt index f9c76ef56..0ec7f8f51 100644 --- a/features/feature_change_record_type/src/main/java/com/example/util/simpletimetracker/feature_change_record_type/view/ChangeRecordTypeFragment.kt +++ b/features/feature_change_record_type/src/main/java/com/example/util/simpletimetracker/feature_change_record_type/view/ChangeRecordTypeFragment.kt @@ -203,6 +203,8 @@ class ChangeRecordTypeFragment : .setOnClick(viewModel::onDuplicateClick) layoutChangeRecordTypeAdditional.checkboxChangeRecordTypeAdditionalInstant .setOnClick(viewModel::onInstantClick) + layoutChangeRecordTypeAdditional.groupChangeRecordTypeAdditionalInstantSelector + .setOnClick(viewModel::onInstantDurationClick) IconSelectionViewDelegate.initUx( viewModel = viewModel, layout = containerChangeRecordTypeIcon, @@ -471,6 +473,9 @@ class ChangeRecordTypeFragment : if (data.isInstantChecked != checkboxChangeRecordTypeAdditionalInstant.isChecked) { checkboxChangeRecordTypeAdditionalInstant.isChecked = data.isInstantChecked } + tvChangeRecordTypeAdditionalInstantDuration.isVisible = data.isInstantChecked + groupChangeRecordTypeAdditionalInstantSelector.isVisible = data.isInstantChecked + tvChangeRecordTypeAdditionalInstantSelectorValue.text = data.instantDuration } companion object { diff --git a/features/feature_change_record_type/src/main/java/com/example/util/simpletimetracker/feature_change_record_type/viewData/ChangeRecordTypeAdditionalState.kt b/features/feature_change_record_type/src/main/java/com/example/util/simpletimetracker/feature_change_record_type/viewData/ChangeRecordTypeAdditionalState.kt index afd450c67..fd685b3c6 100644 --- a/features/feature_change_record_type/src/main/java/com/example/util/simpletimetracker/feature_change_record_type/viewData/ChangeRecordTypeAdditionalState.kt +++ b/features/feature_change_record_type/src/main/java/com/example/util/simpletimetracker/feature_change_record_type/viewData/ChangeRecordTypeAdditionalState.kt @@ -3,4 +3,5 @@ package com.example.util.simpletimetracker.feature_change_record_type.viewData data class ChangeRecordTypeAdditionalState( val isDuplicateVisible: Boolean, val isInstantChecked: Boolean, + val instantDuration: String, ) \ No newline at end of file diff --git a/features/feature_change_record_type/src/main/java/com/example/util/simpletimetracker/feature_change_record_type/viewModel/ChangeRecordTypeViewModel.kt b/features/feature_change_record_type/src/main/java/com/example/util/simpletimetracker/feature_change_record_type/viewModel/ChangeRecordTypeViewModel.kt index 682fb6da7..0702e22ca 100644 --- a/features/feature_change_record_type/src/main/java/com/example/util/simpletimetracker/feature_change_record_type/viewModel/ChangeRecordTypeViewModel.kt +++ b/features/feature_change_record_type/src/main/java/com/example/util/simpletimetracker/feature_change_record_type/viewModel/ChangeRecordTypeViewModel.kt @@ -13,6 +13,7 @@ import com.example.util.simpletimetracker.core.extension.set import com.example.util.simpletimetracker.core.interactor.SnackBarMessageNavigationInteractor import com.example.util.simpletimetracker.core.interactor.StatisticsDetailNavigationInteractor import com.example.util.simpletimetracker.core.mapper.RecordTypeViewDataMapper +import com.example.util.simpletimetracker.core.mapper.TimeMapper import com.example.util.simpletimetracker.core.repo.ResourceRepo import com.example.util.simpletimetracker.core.view.ViewChooserStateDelegate import com.example.util.simpletimetracker.domain.extension.addOrRemove @@ -44,9 +45,11 @@ import com.example.util.simpletimetracker.navigation.Router import com.example.util.simpletimetracker.navigation.params.screen.ChangeCategoryFromChangeActivityParams import com.example.util.simpletimetracker.navigation.params.screen.ChangeRecordTypeParams import com.example.util.simpletimetracker.navigation.params.screen.ChangeTagData +import com.example.util.simpletimetracker.navigation.params.screen.DurationDialogParams import com.example.util.simpletimetracker.navigation.params.screen.StandardDialogParams import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch +import java.util.concurrent.TimeUnit import javax.inject.Inject import com.example.util.simpletimetracker.core.R as coreR @@ -54,6 +57,7 @@ import com.example.util.simpletimetracker.core.R as coreR class ChangeRecordTypeViewModel @Inject constructor( private val router: Router, private val resourceRepo: ResourceRepo, + private val timeMapper: TimeMapper, private val removeRunningRecordMediator: RemoveRunningRecordMediator, private val recordTypeInteractor: RecordTypeInteractor, private val runningRecordInteractor: RunningRecordInteractor, @@ -107,6 +111,7 @@ class ChangeRecordTypeViewModel @Inject constructor( ChangeRecordTypeAdditionalState( isDuplicateVisible = false, isInstantChecked = false, + instantDuration = "", ), ) val archiveButtonEnabled: LiveData = MutableLiveData(true) @@ -124,6 +129,7 @@ class ChangeRecordTypeViewModel @Inject constructor( private var newName: String = "" private var newCategories: MutableList = mutableListOf() private var newIsInstant: Boolean = false + private var newInstantDuration: Long = 0 init { colorSelectionViewModelDelegateImpl.attach(getColorSelectionDelegateParent()) @@ -307,12 +313,31 @@ class ChangeRecordTypeViewModel @Inject constructor( updateAdditionalState() } + fun onInstantDurationClick() = viewModelScope.launch { + DurationDialogParams( + tag = INSTANT_DURATION_DIALOG_TAG, + value = DurationDialogParams.Value.DurationSeconds(getInstantDuration()), + hideDisableButton = true, + ).let(router::navigate) + } + fun onPositiveDialogClick(tag: String?) { when (tag) { DELETE_ALERT_DIALOG_TAG -> delete() } } + fun onDurationSet(tag: String?, duration: Long, anchor: Any) { + goalsViewModelDelegate.onGoalDurationSet(tag, duration, anchor) + onInstantDurationSet(tag, duration) + } + + private fun onInstantDurationSet(tag: String?, duration: Long) { + if (tag != INSTANT_DURATION_DIALOG_TAG) return + newInstantDuration = duration.coerceAtLeast(1) + updateAdditionalState() + } + // TODO check all after actions that need to be done after type delete, // also tag, category, record, running record etc. private fun delete() { @@ -370,6 +395,7 @@ class ChangeRecordTypeViewModel @Inject constructor( icon = iconSelectionViewModelDelegateImpl.newIcon, color = colorSelectionViewModelDelegateImpl.newColor, instant = newIsInstant, + instantDuration = getInstantDuration(), ) return recordTypeInteractor.add(recordType) @@ -396,6 +422,7 @@ class ChangeRecordTypeViewModel @Inject constructor( icon = iconSelectionViewModelDelegateImpl.newIcon, color = colorSelectionViewModelDelegateImpl.newColor, instant = newIsInstant, + instantDuration = getInstantDuration(), ) return recordTypeInteractor.add(recordType) @@ -413,6 +440,7 @@ class ChangeRecordTypeViewModel @Inject constructor( recordTypeInteractor.get(recordTypeId)?.let { newName = it.name newIsInstant = it.instant + newInstantDuration = it.instantDuration iconSelectionViewModelDelegateImpl.newIcon = it.icon colorSelectionViewModelDelegateImpl.newColor = it.color goalsViewModelDelegate.initialize(RecordTypeGoal.IdData.Type(it.id)) @@ -463,6 +491,15 @@ class ChangeRecordTypeViewModel @Inject constructor( snackBarMessageNavigationInteractor.showArchiveMessage(stringResId) } + // It is 0 by default, but can't be zero if enabled. + private fun getInstantDuration(): Long { + return if (newIsInstant) { + newInstantDuration.takeIf { it > 0L } ?: instantDurationDefault + } else { + newInstantDuration + } + } + private suspend fun updateRecordPreviewViewData() { val data = loadRecordPreviewViewData() recordType.set(data) @@ -476,6 +513,7 @@ class ChangeRecordTypeViewModel @Inject constructor( icon = iconSelectionViewModelDelegateImpl.newIcon, color = colorSelectionViewModelDelegateImpl.newColor, instant = newIsInstant, + instantDuration = getInstantDuration(), ).let { recordTypeViewDataMapper.map(it, isDarkTheme) } } @@ -497,10 +535,13 @@ class ChangeRecordTypeViewModel @Inject constructor( return ChangeRecordTypeAdditionalState( isDuplicateVisible = extra is ChangeRecordTypeParams.Change, isInstantChecked = newIsInstant, + instantDuration = timeMapper.formatDuration(getInstantDuration()), ) } companion object { private const val DELETE_ALERT_DIALOG_TAG = "delete_alert_dialog_tag" + private const val INSTANT_DURATION_DIALOG_TAG = "instant_duration_dialog_tag" + private val instantDurationDefault = TimeUnit.MINUTES.toSeconds(1) } } diff --git a/features/feature_change_record_type/src/main/res/layout/change_record_type_additional_layout.xml b/features/feature_change_record_type/src/main/res/layout/change_record_type_additional_layout.xml index aa8c264e6..2da4b3b58 100644 --- a/features/feature_change_record_type/src/main/res/layout/change_record_type_additional_layout.xml +++ b/features/feature_change_record_type/src/main/res/layout/change_record_type_additional_layout.xml @@ -6,6 +6,16 @@ android:layout_height="match_parent" android:orientation="vertical"> + + + + + + + + + + + + \ No newline at end of file diff --git a/features/feature_change_running_record/src/main/java/com/example/util/simpletimetracker/feature_change_running_record/viewModel/ChangeRunningRecordViewModel.kt b/features/feature_change_running_record/src/main/java/com/example/util/simpletimetracker/feature_change_running_record/viewModel/ChangeRunningRecordViewModel.kt index dd70d73ea..ba6c3365a 100644 --- a/features/feature_change_running_record/src/main/java/com/example/util/simpletimetracker/feature_change_running_record/viewModel/ChangeRunningRecordViewModel.kt +++ b/features/feature_change_running_record/src/main/java/com/example/util/simpletimetracker/feature_change_running_record/viewModel/ChangeRunningRecordViewModel.kt @@ -130,7 +130,7 @@ class ChangeRunningRecordViewModel @Inject constructor( override suspend fun onSaveClickDelegate() { // Widgets will update on adding. removeRunningRecordMediator.remove(extra.id, updateWidgets = false) - addRunningRecordMediator.add( + addRunningRecordMediator.addAfterChange( typeId = newTypeId, timeStarted = newTimeStarted, comment = newComment, diff --git a/features/feature_dialogs/src/main/java/com/example/util/simpletimetracker/feature_dialogs/defaultTypesSelection/interactor/GetDefaultRecordTypesInteractor.kt b/features/feature_dialogs/src/main/java/com/example/util/simpletimetracker/feature_dialogs/defaultTypesSelection/interactor/GetDefaultRecordTypesInteractor.kt index f8338c81d..f4cb56352 100644 --- a/features/feature_dialogs/src/main/java/com/example/util/simpletimetracker/feature_dialogs/defaultTypesSelection/interactor/GetDefaultRecordTypesInteractor.kt +++ b/features/feature_dialogs/src/main/java/com/example/util/simpletimetracker/feature_dialogs/defaultTypesSelection/interactor/GetDefaultRecordTypesInteractor.kt @@ -16,6 +16,7 @@ class GetDefaultRecordTypesInteractor @Inject constructor() { icon = type.icon, color = AppColor(colorId = type.colorId, colorInt = ""), instant = false, + instantDuration = 0L, ) } } diff --git a/features/feature_records/src/main/java/com/example/util/simpletimetracker/feature_records/interactor/RecordsViewDataInteractor.kt b/features/feature_records/src/main/java/com/example/util/simpletimetracker/feature_records/interactor/RecordsViewDataInteractor.kt index 1102c4fbb..7249114ad 100644 --- a/features/feature_records/src/main/java/com/example/util/simpletimetracker/feature_records/interactor/RecordsViewDataInteractor.kt +++ b/features/feature_records/src/main/java/com/example/util/simpletimetracker/feature_records/interactor/RecordsViewDataInteractor.kt @@ -34,6 +34,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.lang.Long.min import java.util.Calendar +import java.util.Comparator import java.util.concurrent.TimeUnit import javax.inject.Inject import kotlin.math.max @@ -209,12 +210,19 @@ class RecordsViewDataInteractor @Inject constructor( null } + val sortComparator: Comparator = compareByDescending { + it.timeStartedTimestamp + }.thenBy { + // Otherwise 0 duration activities would be on top of untracked. + it.data.typeId != UNTRACKED_ITEM_ID + } + val items = when { showFirstEnterHint -> listOf(recordViewDataMapper.mapToNoRecords()) records.isEmpty() -> listOf(recordViewDataMapper.mapToEmpty()) else -> { records - .sortedByDescending { it.timeStartedTimestamp } + .sortedWith(sortComparator) .map { it.data.value } + listOfNotNull(hint) } diff --git a/features/feature_views/src/main/java/com/example/util/simpletimetracker/feature_views/RecordView.kt b/features/feature_views/src/main/java/com/example/util/simpletimetracker/feature_views/RecordView.kt index 5d02b4aa1..9b012066e 100644 --- a/features/feature_views/src/main/java/com/example/util/simpletimetracker/feature_views/RecordView.kt +++ b/features/feature_views/src/main/java/com/example/util/simpletimetracker/feature_views/RecordView.kt @@ -60,8 +60,8 @@ class RecordView @JvmOverloads constructor( set(value) { binding.tvRecordItemTimeStarted.text = value binding.tvRecordItemTimeStarted.visible = value.isNotEmpty() - binding.tvRecordItemTimeSeparator.visible = value.isNotEmpty() field = value + updateSeparatorVisibility() } var itemTimeEnded: String = "" @@ -69,6 +69,7 @@ class RecordView @JvmOverloads constructor( binding.tvRecordItemTimeFinished.text = value binding.tvRecordItemTimeFinished.visible = value.isNotEmpty() field = value + updateSeparatorVisibility() } var itemDuration: String = "" @@ -165,4 +166,9 @@ class RecordView @JvmOverloads constructor( tvRecordItemName.text = spannable } } + + private fun updateSeparatorVisibility() { + binding.tvRecordItemTimeSeparator.visible = + itemTimeStarted.isNotEmpty() && itemTimeEnded.isNotEmpty() + } } \ No newline at end of file diff --git a/features/feature_views/src/main/res/layout/record_view_layout.xml b/features/feature_views/src/main/res/layout/record_view_layout.xml index b136cdada..2efc04cbb 100644 --- a/features/feature_views/src/main/res/layout/record_view_layout.xml +++ b/features/feature_views/src/main/res/layout/record_view_layout.xml @@ -35,7 +35,7 @@ android:layout_marginStart="8dp" android:textColor="?appLightTextColor" android:textStyle="bold" - app:layout_constraintBottom_toTopOf="@id/tvRecordItemTimeSeparator" + app:layout_constraintBottom_toTopOf="@id/tvRecordItemTimeStarted" app:layout_constraintEnd_toStartOf="@id/tvRecordItemDuration" app:layout_constraintStart_toEndOf="@id/ivRecordItemIcon" app:layout_constraintTop_toTopOf="parent" @@ -47,6 +47,7 @@ android:layout_height="wrap_content" android:layout_marginStart="8dp" android:textColor="?appLightTextColor" + app:layout_constraintBottom_toTopOf="@id/tvRecordItemComment" app:layout_constraintStart_toEndOf="@id/ivRecordItemIcon" app:layout_constraintTop_toBottomOf="@id/tvRecordItemName" tools:text="07:35" /> @@ -95,7 +96,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/tvRecordItemDuration" app:layout_constraintStart_toEndOf="@id/ivRecordItemIcon" - app:layout_constraintTop_toBottomOf="@id/tvRecordItemTimeSeparator" + app:layout_constraintTop_toBottomOf="@id/tvRecordItemTimeStarted" tools:text="Record comment record comment record comment record comment" tools:visibility="visible" /> diff --git a/resources/src/main/res/values-ar/strings.xml b/resources/src/main/res/values-ar/strings.xml index 806a961fe..0642c3710 100644 --- a/resources/src/main/res/values-ar/strings.xml +++ b/resources/src/main/res/values-ar/strings.xml @@ -156,8 +156,9 @@ الهدف إضافي فوري - الأنشطة الفورية ليس لها مدة ويتم تتبعها فورًا عند النقر. + الأنشطة الفورية لها مدة افتراضية ويتم تتبعها فور النقر. سيظهر الإخطار بعد الوصول إلى الهدف + سيتم إنشاء نسخة من هذا النشاط الجلسة @string/range_day @string/range_week diff --git a/resources/src/main/res/values-ca/strings.xml b/resources/src/main/res/values-ca/strings.xml index 78fc7dd7c..12b0f7d88 100644 --- a/resources/src/main/res/values-ca/strings.xml +++ b/resources/src/main/res/values-ca/strings.xml @@ -156,8 +156,9 @@ Objectiu Addicional Instant - Les activitats instantànies no tenen durada i es fan un seguiment immediatament en fer clic. + Les activitats instantànies tenen una durada predeterminada i es fan un seguiment immediatament en fer clic. La notificació es mostrarà després d\'arribar a l\'objectiu + Crearà una còpia d\'aquesta activitat Sessió @string/range_day @string/range_week diff --git a/resources/src/main/res/values-de/strings.xml b/resources/src/main/res/values-de/strings.xml index c88c7dfed..51e415294 100644 --- a/resources/src/main/res/values-de/strings.xml +++ b/resources/src/main/res/values-de/strings.xml @@ -156,8 +156,9 @@ Ziel Zusätzlich Sofort - Sofortige Aktivitäten haben keine Dauer und werden sofort beim Klicken verfolgt. + Sofortige Aktivitäten haben eine Standarddauer und werden sofort beim Klicken verfolgt. Nach Erreichen des Ziels wird eine Benachrichtigung angezeigt + Es wird eine Kopie dieser Aktivität erstellt Session @string/range_day @string/range_week diff --git a/resources/src/main/res/values-es/strings.xml b/resources/src/main/res/values-es/strings.xml index 1defd98a4..a1b26331e 100644 --- a/resources/src/main/res/values-es/strings.xml +++ b/resources/src/main/res/values-es/strings.xml @@ -156,8 +156,9 @@ Objetivo Adicional Instante - Las actividades instantáneas no tienen duración y se rastrean inmediatamente al hacer clic. + Las actividades instantáneas tienen una duración predeterminada y se rastrean inmediatamente al hacer clic. La notificación se mostrará después de alcanzar la meta. + Creará una copia de esta actividad Sesión @string/range_day @string/range_week diff --git a/resources/src/main/res/values-fa/strings.xml b/resources/src/main/res/values-fa/strings.xml index 8049493fc..7c7df5fce 100644 --- a/resources/src/main/res/values-fa/strings.xml +++ b/resources/src/main/res/values-fa/strings.xml @@ -156,8 +156,9 @@ هدف اضافی فوری - فعالیت های فوری مدت زمان ندارند و بلافاصله با کلیک ردیابی می شوند. + فعالیت های فوری دارای مدت زمان پیش فرض هستند و بلافاصله پس از کلیک ردیابی می شوند. اعلان پس از رسیدن به هدف نشان داده می شود + یک کپی از این فعالیت ایجاد خواهد کرد جلسه @string/range_day @string/range_week diff --git a/resources/src/main/res/values-fr/strings.xml b/resources/src/main/res/values-fr/strings.xml index bcb333730..429c9dd39 100644 --- a/resources/src/main/res/values-fr/strings.xml +++ b/resources/src/main/res/values-fr/strings.xml @@ -156,8 +156,9 @@ Objectif Supplémentaire Instantané - Les activités instantanées n\'ont pas de durée et sont suivies immédiatement au clic. + Les activités instantanées ont une durée par défaut et sont suivies immédiatement au clic. Une notification sera affichée après avoir atteint l\'objectif + Créera une copie de cette activité Session @string/range_day @string/range_week diff --git a/resources/src/main/res/values-hi/strings.xml b/resources/src/main/res/values-hi/strings.xml index bac941deb..11889a9d4 100644 --- a/resources/src/main/res/values-hi/strings.xml +++ b/resources/src/main/res/values-hi/strings.xml @@ -156,8 +156,9 @@ लक्ष्य अतिरिक्त तुरंत - त्वरित गतिविधियों की कोई अवधि नहीं होती और क्लिक पर तुरंत ट्रैक किया जाता है। + त्वरित गतिविधियों की डिफ़ॉल्ट अवधि होती है और क्लिक पर तुरंत ट्रैक किया जाता है। लक्ष्य तक पहुंचने के बाद अधिसूचना दिखाई जाएगी + इस गतिविधि की एक प्रति बनाएंगे सत्र @string/range_day @string/range_week diff --git a/resources/src/main/res/values-in/strings.xml b/resources/src/main/res/values-in/strings.xml index 9742a0ca7..fc91bacd7 100644 --- a/resources/src/main/res/values-in/strings.xml +++ b/resources/src/main/res/values-in/strings.xml @@ -156,8 +156,9 @@ Tujuan Tambahan Instan - Aktivitas instan tidak memiliki durasi dan langsung dilacak saat diklik. + Aktivitas instan memiliki durasi default dan langsung dilacak saat diklik. Pemberitahuan akan ditampilkan setelah mencapai tujuan + Akan membuat salinan aktivitas ini Sidang @string/range_day @string/range_week diff --git a/resources/src/main/res/values-it/strings.xml b/resources/src/main/res/values-it/strings.xml index d9653761a..d7c663c19 100644 --- a/resources/src/main/res/values-it/strings.xml +++ b/resources/src/main/res/values-it/strings.xml @@ -156,8 +156,9 @@ Obiettivo Ulteriori Istantaneo - Le attività istantanee non hanno durata e vengono tracciate immediatamente al clic. + Le attività istantanee hanno una durata predefinita e vengono tracciate immediatamente al clic. La notifica verrà mostrata dopo aver raggiunto l\'obiettivo + Creerà una copia di questa attività Sessione @string/range_day @string/range_week diff --git a/resources/src/main/res/values-ja/strings.xml b/resources/src/main/res/values-ja/strings.xml index 4e8605674..396c576e0 100644 --- a/resources/src/main/res/values-ja/strings.xml +++ b/resources/src/main/res/values-ja/strings.xml @@ -156,8 +156,9 @@ 目標 追加 インスタント - インスタントアクティビティには期間がなく、クリックするとすぐに追跡されます。 + インスタントアクティビティにはデフォルトの期間があり、クリックするとすぐに追跡されます。 目標達成後に通知が表示されます + このアクティビティのコピーを作成します セッション @string/range_day @string/range_week diff --git a/resources/src/main/res/values-ko/strings.xml b/resources/src/main/res/values-ko/strings.xml index a01ea8f39..b46172321 100644 --- a/resources/src/main/res/values-ko/strings.xml +++ b/resources/src/main/res/values-ko/strings.xml @@ -156,8 +156,9 @@ 목표 추가의 즉각적인 - 인스턴트 활동에는 기간이 없으며 클릭 시 즉시 추적됩니다. + 인스턴트 활동에는 기본 기간이 있으며 클릭 시 즉시 추적됩니다. 목표 달성시 알림을 보냅니다. + 이 활동의 사본을 생성합니다. 세션 @string/range_day @string/range_week diff --git a/resources/src/main/res/values-nl/strings.xml b/resources/src/main/res/values-nl/strings.xml index 0dc17b6ef..465f2ab7c 100644 --- a/resources/src/main/res/values-nl/strings.xml +++ b/resources/src/main/res/values-nl/strings.xml @@ -156,8 +156,9 @@ Doel Aanvullend Direct - Instantactiviteiten hebben geen duur en worden onmiddellijk bijgehouden na klikken. + Directe activiteiten hebben een standaardduur en worden onmiddellijk bijgehouden na klikken. Melding wordt getoond na het bereiken van het doel + Er wordt een kopie van deze activiteit gemaakt Sessie @string/range_day @string/range_week diff --git a/resources/src/main/res/values-pl/strings.xml b/resources/src/main/res/values-pl/strings.xml index 16d5d6348..32eab10d8 100644 --- a/resources/src/main/res/values-pl/strings.xml +++ b/resources/src/main/res/values-pl/strings.xml @@ -156,8 +156,9 @@ Cel Dodatkowy Natychmiastowy - Działania natychmiastowe nie mają czasu trwania i są śledzone natychmiast po kliknięciu. + Działania błyskawiczne mają domyślny czas trwania i są śledzone natychmiast po kliknięciu. Powiadomienie wyświetli się po osiągnięciu celu + Utworzy kopię tego działania Sesja @string/range_day @string/range_week diff --git a/resources/src/main/res/values-pt-rPT/strings.xml b/resources/src/main/res/values-pt-rPT/strings.xml index f6644f0c6..975c34086 100644 --- a/resources/src/main/res/values-pt-rPT/strings.xml +++ b/resources/src/main/res/values-pt-rPT/strings.xml @@ -156,8 +156,9 @@ Meta Adicional Instantâneo - As atividades instantâneas não têm duração e são rastreadas imediatamente ao clicar. + As atividades instantâneas têm duração padrão e são rastreadas imediatamente após o clique. A notificação será exibida depois da meta atingida + Criará uma cópia desta atividade Sessão @string/range_day @string/range_week diff --git a/resources/src/main/res/values-pt/strings.xml b/resources/src/main/res/values-pt/strings.xml index b5a965e49..bdea894b1 100644 --- a/resources/src/main/res/values-pt/strings.xml +++ b/resources/src/main/res/values-pt/strings.xml @@ -156,8 +156,9 @@ Meta Adicional Instantâneo - As atividades instantâneas não têm duração e são rastreadas imediatamente ao clicar. + As atividades instantâneas têm duração padrão e são rastreadas imediatamente após o clique. A notificação será exibida após atingir a meta + Criará uma cópia desta atividade Sessão @string/range_day @string/range_week diff --git a/resources/src/main/res/values-ro/strings.xml b/resources/src/main/res/values-ro/strings.xml index 0b00ca161..584ff4111 100644 --- a/resources/src/main/res/values-ro/strings.xml +++ b/resources/src/main/res/values-ro/strings.xml @@ -156,8 +156,9 @@ Țel Adiţional Instant - Activitățile instantanee nu au durată și sunt urmărite imediat la clic. + Activitățile instantanee au o durată implicită și sunt urmărite imediat la clic. Notificările vor fi vizibile după atingerea țelului + Va crea o copie a acestei activități Sesiune @string/range_day @string/range_week diff --git a/resources/src/main/res/values-ru/strings.xml b/resources/src/main/res/values-ru/strings.xml index 706089dc4..8da2535b6 100644 --- a/resources/src/main/res/values-ru/strings.xml +++ b/resources/src/main/res/values-ru/strings.xml @@ -155,9 +155,10 @@ Выкл. Цель Дополнительно - Мгновенный - Мгновенные действия не имеют продолжительности и отслеживаются сразу по клику. + Мгновенное + Мгновенные активности имеют продолжительность по-умолчанию и отслеживаются сразу по клику. Уведомление будет показано после достижения цели + Создаст копию этой активности Сессия @string/range_day @string/range_week diff --git a/resources/src/main/res/values-sv/strings.xml b/resources/src/main/res/values-sv/strings.xml index 0f4555ba4..326e39bb5 100644 --- a/resources/src/main/res/values-sv/strings.xml +++ b/resources/src/main/res/values-sv/strings.xml @@ -156,8 +156,9 @@ Mål Ytterligare Omedelbar - Omedelbara aktiviteter har ingen varaktighet och spåras direkt vid klick. + Omedelbara aktiviteter har standardvaraktighet och spåras direkt vid klick. Avisering kommer att visas efter att du har nått målet + Kommer att skapa en kopia av denna aktivitet Session @string/range_day @string/range_week diff --git a/resources/src/main/res/values-tr/strings.xml b/resources/src/main/res/values-tr/strings.xml index e706eaa04..a40008861 100644 --- a/resources/src/main/res/values-tr/strings.xml +++ b/resources/src/main/res/values-tr/strings.xml @@ -156,8 +156,9 @@ Hedef Ek olarak Ani - Anlık aktivitelerin süresi yoktur ve tıklandığında anında izlenir. + Anlık etkinliklerin varsayılan süresi vardır ve tıklandığında hemen izlenir. Hedefe ulaştıktan sonra bildirim gösterilecek + Bu aktivitenin bir kopyasını oluşturacak Dönem @string/range_day @string/range_week diff --git a/resources/src/main/res/values-uk/strings.xml b/resources/src/main/res/values-uk/strings.xml index 26c4d4ad6..0983bc341 100644 --- a/resources/src/main/res/values-uk/strings.xml +++ b/resources/src/main/res/values-uk/strings.xml @@ -156,8 +156,9 @@ Ціль Додатковий Миттєво - Миттєві дії не мають тривалості та відстежуються одразу після натискання. + Миттєві дії мають тривалість за умовчанням і відстежуються одразу після натискання. Сповіщення буде показано після досягнення мети + Буде створено копію цієї діяльності Сесія @string/range_day @string/range_week diff --git a/resources/src/main/res/values-vi/strings.xml b/resources/src/main/res/values-vi/strings.xml index b3adc8166..0374df209 100644 --- a/resources/src/main/res/values-vi/strings.xml +++ b/resources/src/main/res/values-vi/strings.xml @@ -156,8 +156,9 @@ Mục tiêu Thêm vào Lập tức - Hoạt động tức thì không có thời lượng và được theo dõi ngay lập tức khi nhấp chuột. + Hoạt động tức thì có thời lượng mặc định và được theo dõi ngay lập tức khi nhấp chuột. Thông báo sẽ được hiển thị sau khi đạt được mục tiêu + Sẽ tạo một bản sao của hoạt động này Phiên @string/range_day @string/range_week diff --git a/resources/src/main/res/values-zh-rTW/strings.xml b/resources/src/main/res/values-zh-rTW/strings.xml index 243fadb10..930a7fb54 100644 --- a/resources/src/main/res/values-zh-rTW/strings.xml +++ b/resources/src/main/res/values-zh-rTW/strings.xml @@ -156,8 +156,9 @@ 目標 額外的 立即的 - 即時活動沒有持續時間,點擊後立即進行追蹤。 + 即時活動具有預設持續時間,並在點擊後立即進行追蹤。 達到目標後將顯示通知 + 將建立此活動的副本 節段 @string/range_day @string/range_week diff --git a/resources/src/main/res/values-zh/strings.xml b/resources/src/main/res/values-zh/strings.xml index ad5457906..e235aec1e 100644 --- a/resources/src/main/res/values-zh/strings.xml +++ b/resources/src/main/res/values-zh/strings.xml @@ -156,8 +156,9 @@ 目标 额外的 立即的 - 即时活动没有持续时间,点击后立即进行跟踪。 + 即时活动具有默认持续时间,并在点击后立即进行跟踪。 达到目标后将显示通知 + 将创建此活动的副本 阶段 @string/range_day @string/range_week diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index 390686639..fee929835 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -156,8 +156,9 @@ Goal Additional Instant - Instant activities have no duration and tracked immediately on click. + Instant activities have default duration and tracked immediately on click. Notification will be shown after reaching the goal + Will create a copy of this activity Session @string/range_day @string/range_week