diff --git a/app/src/androidTest/java/com/example/util/simpletimetracker/DataEditTest.kt b/app/src/androidTest/java/com/example/util/simpletimetracker/DataEditTest.kt index 38527d807..784ec94d7 100644 --- a/app/src/androidTest/java/com/example/util/simpletimetracker/DataEditTest.kt +++ b/app/src/androidTest/java/com/example/util/simpletimetracker/DataEditTest.kt @@ -16,6 +16,8 @@ import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withSubstring import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.example.util.simpletimetracker.core.extension.setToStartOfDay +import com.example.util.simpletimetracker.core.extension.setWeekToFirstDay import com.example.util.simpletimetracker.feature_base_adapter.R import com.example.util.simpletimetracker.utils.BaseUiTest import com.example.util.simpletimetracker.utils.NavUtils @@ -34,10 +36,13 @@ import dagger.hilt.android.testing.HiltAndroidTest import org.hamcrest.CoreMatchers.allOf import org.junit.Test import org.junit.runner.RunWith +import java.util.Calendar +import java.util.concurrent.TimeUnit import com.example.util.simpletimetracker.core.R as coreR import com.example.util.simpletimetracker.feature_base_adapter.R as baseR import com.example.util.simpletimetracker.feature_data_edit.R as dataEditR import com.example.util.simpletimetracker.feature_records.R as recordsR +import com.example.util.simpletimetracker.feature_records_filter.R as recordsFilterR @Suppress("SameParameterValue") @HiltAndroidTest @@ -700,6 +705,90 @@ class DataEditTest : BaseUiTest() { checkViewIsDisplayed(withText(R.string.no_records_exist)) } + @Test + fun selectByDate() { + NavUtils.openSettingsScreen() + NavUtils.openDataEditScreen() + clickOnViewWithText(coreR.string.data_edit_select_records) + clickOnViewWithText(coreR.string.date_time_dialog_date) + + // Check ranges + clickOnViewWithText(R.string.title_today) + var calendar = Calendar.getInstance().apply { + timeInMillis = System.currentTimeMillis() + setToStartOfDay() + } + var timeStarted = calendar.timeInMillis.formatDateTimeYear() + var timeEnded = (calendar.timeInMillis + TimeUnit.DAYS.toMillis(1)).formatDateTimeYear() + checkViewIsDisplayed(allOf(withId(recordsFilterR.id.tvRecordsFilterRangeTimeStarted), withText(timeStarted))) + checkViewIsDisplayed(allOf(withId(recordsFilterR.id.tvRecordsFilterRangeTimeEnded), withText(timeEnded))) + + clickOnViewWithText(R.string.title_this_week) + calendar = Calendar.getInstance().apply { + timeInMillis = System.currentTimeMillis() + setToStartOfDay() + setWeekToFirstDay() + } + timeStarted = calendar.timeInMillis.formatDateTimeYear() + timeEnded = (calendar.timeInMillis + TimeUnit.DAYS.toMillis(7)).formatDateTimeYear() + checkViewIsDisplayed(allOf(withId(recordsFilterR.id.tvRecordsFilterRangeTimeStarted), withText(timeStarted))) + checkViewIsDisplayed(allOf(withId(recordsFilterR.id.tvRecordsFilterRangeTimeEnded), withText(timeEnded))) + + clickOnViewWithText(R.string.title_this_month) + calendar = Calendar.getInstance().apply { + timeInMillis = System.currentTimeMillis() + setToStartOfDay() + set(Calendar.DAY_OF_MONTH, 1) + } + timeStarted = calendar.timeInMillis.formatDateTimeYear() + calendar.apply { + add(Calendar.MONTH, 1) + } + timeEnded = calendar.timeInMillis.formatDateTimeYear() + checkViewIsDisplayed(allOf(withId(recordsFilterR.id.tvRecordsFilterRangeTimeStarted), withText(timeStarted))) + checkViewIsDisplayed(allOf(withId(recordsFilterR.id.tvRecordsFilterRangeTimeEnded), withText(timeEnded))) + + clickOnViewWithText(R.string.title_this_year) + calendar = Calendar.getInstance().apply { + timeInMillis = System.currentTimeMillis() + setToStartOfDay() + set(Calendar.DAY_OF_YEAR, 1) + } + timeStarted = calendar.timeInMillis.formatDateTimeYear() + calendar.apply { + add(Calendar.YEAR, 1) + } + timeEnded = calendar.timeInMillis.formatDateTimeYear() + checkViewIsDisplayed(allOf(withId(recordsFilterR.id.tvRecordsFilterRangeTimeStarted), withText(timeStarted))) + checkViewIsDisplayed(allOf(withId(recordsFilterR.id.tvRecordsFilterRangeTimeEnded), withText(timeEnded))) + + clickOnViewWithText(R.string.range_overall) + calendar = Calendar.getInstance().apply { + timeInMillis = 0 + } + timeStarted = calendar.timeInMillis.formatDateTimeYear() + calendar.apply { + timeInMillis = System.currentTimeMillis() + } + timeEnded = calendar.timeInMillis.formatDateTimeYear() + checkViewIsDisplayed(allOf(withId(recordsFilterR.id.tvRecordsFilterRangeTimeStarted), withText(timeStarted))) + checkViewIsDisplayed(allOf(withId(recordsFilterR.id.tvRecordsFilterRangeTimeEnded), withText(timeEnded))) + + clickOnViewWithText(getQuantityString(R.plurals.range_last, 7, 7)) + calendar = Calendar.getInstance().apply { + timeInMillis = System.currentTimeMillis() + setToStartOfDay() + } + timeStarted = (calendar.timeInMillis - TimeUnit.DAYS.toMillis(6)).formatDateTimeYear() + timeEnded = (calendar.timeInMillis + TimeUnit.DAYS.toMillis(1)).formatDateTimeYear() + checkViewIsDisplayed(allOf(withId(recordsFilterR.id.tvRecordsFilterRangeTimeStarted), withText(timeStarted))) + checkViewIsDisplayed(allOf(withId(recordsFilterR.id.tvRecordsFilterRangeTimeEnded), withText(timeEnded))) + + clickOnView(allOf(isDescendantOfA(withId(R.id.viewFilterItem)), withText(R.string.range_custom))) + checkViewIsDisplayed(allOf(withId(recordsFilterR.id.tvRecordsFilterRangeTimeStarted), withText(timeStarted))) + checkViewIsDisplayed(allOf(withId(recordsFilterR.id.tvRecordsFilterRangeTimeEnded), withText(timeEnded))) + } + private fun checkRecord( indexes: List, name: String, diff --git a/app/src/androidTest/java/com/example/util/simpletimetracker/IconTest.kt b/app/src/androidTest/java/com/example/util/simpletimetracker/IconTest.kt index 8361fb2b6..9011d347e 100644 --- a/app/src/androidTest/java/com/example/util/simpletimetracker/IconTest.kt +++ b/app/src/androidTest/java/com/example/util/simpletimetracker/IconTest.kt @@ -11,6 +11,7 @@ import com.example.util.simpletimetracker.domain.model.IconEmojiType import com.example.util.simpletimetracker.domain.model.IconImageType import com.example.util.simpletimetracker.utils.BaseUiTest import com.example.util.simpletimetracker.utils.NavUtils +import com.example.util.simpletimetracker.utils.checkViewDoesNotExist import com.example.util.simpletimetracker.utils.checkViewIsDisplayed import com.example.util.simpletimetracker.utils.clickOnRecyclerItem import com.example.util.simpletimetracker.utils.clickOnView @@ -18,6 +19,7 @@ import com.example.util.simpletimetracker.utils.clickOnViewWithId import com.example.util.simpletimetracker.utils.clickOnViewWithText import com.example.util.simpletimetracker.utils.collapseToolbar import com.example.util.simpletimetracker.utils.longClickOnView +import com.example.util.simpletimetracker.utils.nthChildOf import com.example.util.simpletimetracker.utils.recyclerItemCount import com.example.util.simpletimetracker.utils.scrollRecyclerToView import com.example.util.simpletimetracker.utils.swipeUp @@ -385,4 +387,100 @@ class IconTest : BaseUiTest() { ), ) } + + @Test + fun favouriteIcons() { + tryAction { clickOnViewWithText(coreR.string.running_records_add_type) } + + // Check icons + clickOnViewWithText(coreR.string.change_record_type_icon_image_hint) + + // Check first category + checkViewIsDisplayed( + allOf( + nthChildOf(withId(R.id.rvIconSelection), 0), + withText(R.string.imageGroupMaps), + ), + ) + + // Add to favourites + val (category, images) = iconImageMapper.getAvailableImages(loadSearchHints = false) + .toList().dropLast(1).last() + clickOnView(withTag(category.categoryIcon)) + val firstImage = images.first().iconResId + checkViewIsDisplayed(withText(category.name)) + clickOnView(withTag(firstImage)) + clickOnViewWithId(R.id.btnIconSelectionFavourite) + clickOnView(withTag(R.drawable.icon_category_image_favourite)) + checkViewIsDisplayed( + allOf( + nthChildOf(withId(R.id.rvIconSelection), 0), + withText(R.string.change_record_favourite_comments_hint), + ), + ) + checkViewIsDisplayed( + allOf( + nthChildOf(withId(R.id.rvIconSelection), 1), + hasDescendant(withTag(firstImage)), + ), + ) + + // Remove from favourites + clickOnViewWithId(R.id.btnIconSelectionFavourite) + checkViewDoesNotExist(withTag(R.drawable.icon_category_image_favourite)) + checkViewIsDisplayed( + allOf( + nthChildOf(withId(R.id.rvIconSelection), 0), + withText(R.string.imageGroupMaps), + ), + ) + + // Check emojis + clickOnView( + allOf( + isDescendantOfA(withId(changeRecordTypeR.id.btnIconSelectionSwitch)), + withText(coreR.string.change_record_type_icon_emoji_hint), + ), + ) + + // Check first category + checkViewIsDisplayed( + allOf( + nthChildOf(withId(R.id.rvIconSelection), 0), + withText(R.string.emojiGroupSmileys), + ), + ) + + // Add to favourites + val (emojiCategory, emojis) = iconEmojiMapper.getAvailableEmojis(loadSearchHints = false) + .toList().last() + clickOnView(withTag(emojiCategory.categoryIcon)) + val firstEmoji = emojis.first().emojiCode + checkViewIsDisplayed(withText(emojiCategory.name)) + clickOnView(withText(firstEmoji)) + clickOnViewWithId(R.id.btnIconSelectionFavourite) + clickOnView(withTag(R.drawable.icon_category_image_favourite)) + checkViewIsDisplayed( + allOf( + nthChildOf(withId(R.id.rvIconSelection), 0), + withText(R.string.change_record_favourite_comments_hint), + ), + ) + checkViewIsDisplayed( + allOf( + nthChildOf(withId(R.id.rvIconSelection), 1), + hasDescendant(withText(firstEmoji)), + ), + ) + + // Remove from favourites + clickOnViewWithId(R.id.btnIconSelectionFavourite) + checkViewDoesNotExist(withTag(R.drawable.icon_category_image_favourite)) + checkViewIsDisplayed( + allOf( + nthChildOf(withId(R.id.rvIconSelection), 0), + withText(R.string.emojiGroupSmileys), + ), + ) + } } diff --git a/app/src/androidTest/java/com/example/util/simpletimetracker/RecordActionsContinueTest.kt b/app/src/androidTest/java/com/example/util/simpletimetracker/RecordActionsContinueTest.kt index a2a010a92..b741509a8 100644 --- a/app/src/androidTest/java/com/example/util/simpletimetracker/RecordActionsContinueTest.kt +++ b/app/src/androidTest/java/com/example/util/simpletimetracker/RecordActionsContinueTest.kt @@ -355,12 +355,49 @@ class RecordActionsContinueTest : BaseUiTest() { clickOnViewWithText(coreR.string.change_record_continue) // Running record stopped - checkViewIsDisplayed(allOf(withText(name1), isCompletelyDisplayed())) + checkViewIsDisplayed( + allOf( + withId(baseR.id.viewRecordItem), + hasDescendant(withText(name1)), + hasDescendant(withText("0$minuteString")), + isCompletelyDisplayed(), + ), + ) + + // New running record + NavUtils.openRunningRecordsScreen() + checkViewIsDisplayed( + allOf( + withId(baseR.id.viewRunningRecordItem), + hasDescendant(withText(name2)), + isCompletelyDisplayed(), + ), + ) + } + + @Test + fun continueDefaultDuration() { + val name = "name" + + // Setup + testUtils.addActivity( + name = name, + defaultDuration = TimeUnit.MINUTES.toMillis(5), + ) + Thread.sleep(1000) + tryAction { clickOnViewWithText(name) } + + // Continue + NavUtils.openRecordsScreen() + clickOnView(allOf(withText(name), isCompletelyDisplayed())) + clickOnViewWithText(coreR.string.change_record_actions_hint) + scrollRecyclerToView(changeRecordR.id.rvChangeRecordAction, withText(coreR.string.change_record_continue)) + clickOnViewWithText(coreR.string.change_record_continue) // New running record NavUtils.openRunningRecordsScreen() checkViewIsDisplayed( - allOf(withId(baseR.id.viewRunningRecordItem), hasDescendant(withText(name2)), isCompletelyDisplayed()), + allOf(withId(baseR.id.viewRunningRecordItem), hasDescendant(withText(name)), isCompletelyDisplayed()), ) } diff --git a/app/src/androidTest/java/com/example/util/simpletimetracker/RecordActionsSplitTest.kt b/app/src/androidTest/java/com/example/util/simpletimetracker/RecordActionsSplitTest.kt index 3d0a3d5ed..876cab8c7 100644 --- a/app/src/androidTest/java/com/example/util/simpletimetracker/RecordActionsSplitTest.kt +++ b/app/src/androidTest/java/com/example/util/simpletimetracker/RecordActionsSplitTest.kt @@ -2,6 +2,7 @@ package com.example.util.simpletimetracker import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.pressBack +import androidx.test.espresso.action.GeneralLocation import androidx.test.espresso.matcher.ViewMatchers.hasDescendant import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed import androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA @@ -13,6 +14,7 @@ import com.example.util.simpletimetracker.utils.BaseUiTest import com.example.util.simpletimetracker.utils.NavUtils import com.example.util.simpletimetracker.utils.checkViewDoesNotExist import com.example.util.simpletimetracker.utils.checkViewIsDisplayed +import com.example.util.simpletimetracker.utils.clickLocation import com.example.util.simpletimetracker.utils.clickOnRecyclerItem import com.example.util.simpletimetracker.utils.clickOnView import com.example.util.simpletimetracker.utils.clickOnViewWithId @@ -76,6 +78,17 @@ class RecordActionsSplitTest : BaseUiTest() { clickOnAdjustment("+5") timePreview = calendar.apply { add(Calendar.MINUTE, +5) }.timeInMillis.formatDateTime() checkViewIsDisplayed(allOf(withId(changeRecordR.id.tvChangeRecordTimePreviewItem), withText(timePreview))) + + // Check slider + onView(withId(changeRecordR.id.sliderChangeRecordItem)).perform(clickLocation(GeneralLocation.CENTER_LEFT)) + timePreview = timeStartedTimestamp.formatDateTime() + checkViewIsDisplayed(allOf(withId(changeRecordR.id.tvChangeRecordTimePreviewItem), withText(timePreview))) + onView(withId(changeRecordR.id.sliderChangeRecordItem)).perform(clickLocation(GeneralLocation.CENTER_RIGHT)) + timePreview = timeEndedTimestamp.formatDateTime() + checkViewIsDisplayed(allOf(withId(changeRecordR.id.tvChangeRecordTimePreviewItem), withText(timePreview))) + onView(withId(changeRecordR.id.sliderChangeRecordItem)).perform(clickLocation(GeneralLocation.CENTER)) + timePreview = calendar.getMillis(16, 17).formatDateTime() + checkViewIsDisplayed(allOf(withId(changeRecordR.id.tvChangeRecordTimePreviewItem), withText(timePreview))) } @Test diff --git a/app/src/androidTest/java/com/example/util/simpletimetracker/SettingsBackupTest.kt b/app/src/androidTest/java/com/example/util/simpletimetracker/SettingsBackupTest.kt index c63506a16..03153db29 100644 --- a/app/src/androidTest/java/com/example/util/simpletimetracker/SettingsBackupTest.kt +++ b/app/src/androidTest/java/com/example/util/simpletimetracker/SettingsBackupTest.kt @@ -21,6 +21,8 @@ import androidx.test.espresso.matcher.ViewMatchers.withSubstring import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 import com.example.util.simpletimetracker.core.mapper.ColorMapper +import com.example.util.simpletimetracker.domain.model.ActivityFilter +import com.example.util.simpletimetracker.domain.model.ComplexRule import com.example.util.simpletimetracker.domain.model.DayOfWeek import com.example.util.simpletimetracker.feature_dialogs.dateTime.CustomDatePicker import com.example.util.simpletimetracker.utils.BaseUiTest @@ -253,7 +255,7 @@ class SettingsBackupTest : BaseUiTest() { clickOnViewWithId(R.id.btnRecordAdd) onView(withText(R.string.change_record_comment_field)).perform(nestedScrollTo()) clickOnViewWithText(R.string.change_record_comment_field) - checkViewIsDisplayed(withText(com.example.util.simpletimetracker.core.R.string.change_record_favourite_comments_hint)) + checkViewIsDisplayed(withText(R.string.change_record_favourite_comments_hint)) commentList.forEach { checkViewIsDisplayed( allOf(withId(changeRecordR.id.tvChangeRecordItemComment), withText(it.comment)), @@ -315,7 +317,7 @@ class SettingsBackupTest : BaseUiTest() { checkViewIsNotDisplayed(withId(changeRecordTypeR.id.layoutChangeRecordTypeGoalPreview)) pressBack() longClickOnView(allOf(withText("type3"), isCompletelyDisplayed())) - clickOnViewWithText(com.example.util.simpletimetracker.core.R.string.change_record_type_goal_time_hint) + clickOnViewWithText(R.string.change_record_type_goal_time_hint) onView( allOf( isDescendantOfA(withId(changeRecordTypeR.id.layoutChangeRecordTypeGoalSession)), @@ -526,6 +528,159 @@ class SettingsBackupTest : BaseUiTest() { activityList.drop(3).forEach { checkViewDoesNotExist(withText(it.name)) } } + @Suppress("ReplaceGetOrSet", "ComplexRedundantLet") + @Test + fun partialRestoreWithExistingData() { + val colors = ColorMapper.getAvailableColors() + + // Add data + activityList.take(2).forEach { + testUtils.addActivity( + name = it.name, + color = (it.color as ColorTestData.Position).value.let(colors::get), + text = it.icon, + ) + } + categoryList.first().let { + testUtils.addCategory( + tagName = it.name, + color = (it.color as ColorTestData.Position).value.let(colors::get), + ) + } + tagList.first().let { + testUtils.addRecordTag( + tagName = it.name, + typeName = "type1", + color = (it.color as ColorTestData.Position).value.let(colors::get), + icon = R.drawable.ic_360_24px, + ) + } + recordList.first().let { + testUtils.addRecord( + typeName = "type1", + timeStarted = 1727096400000, + timeEnded = 1727100000000, + ) + } + activityFilterList.first().let { + testUtils.addActivityFilter( + name = it.name, + type = ActivityFilter.Type.Activity, + color = (it.color as ColorTestData.Position).value.let(colors::get), + names = listOf("type1", "type2"), + ) + } + commentList.first().let { + testUtils.addFavouriteComment(it.comment) + } + iconsList.first().let { + testUtils.addFavouriteIcon(it.icon) + } + colorsList.first().let { + testUtils.addFavouriteColor(it.colorInt) + } + ruleList.first().let { + testUtils.addComplexRule( + action = ComplexRule.Action.AllowMultitasking, + startingTypeNames = listOf("type1"), + currentTypeNames = listOf("type2"), + daysOfWeek = listOf(DayOfWeek.SUNDAY, DayOfWeek.MONDAY), + ) + } + + // Restore + NavUtils.openSettingsScreen() + NavUtils.openSettingsBackup() + scrollSettingsRecyclerToText(R.string.settings_backup_options) + clickOnSettingsRecyclerText(R.string.settings_backup_options) + clickOnViewWithText(R.string.backup_options_partial_restore) + + // Check data counts + checkViewIsDisplayed(withText("${getString(R.string.activity_hint)}(2)")) + checkViewIsDisplayed(withText("${getString(R.string.category_hint)}(2)")) + checkViewIsDisplayed(withText("${getString(R.string.record_tag_hint_short)}(3)")) + checkViewIsDisplayed(withText("${getString(R.string.shortcut_navigation_records)}(3)")) + checkViewIsDisplayed(withText("${getString(R.string.change_activity_filters_hint)}(1)")) + checkViewIsDisplayed(withText("${getString(R.string.change_record_favourite_comments_hint_long)}(1)")) + checkViewIsDisplayed(withText("${getString(R.string.change_record_favourite_icons_hint)}(1)")) + checkViewIsDisplayed(withText("${getString(R.string.change_record_favourite_colors_hint)}(1)")) + checkViewIsDisplayed(withText("${getString(R.string.settings_complex_rules)}(2)")) + + // Check filtering + // Activities + clickOnViewWithText("${getString(R.string.activity_hint)}(2)") + checkActivities(activityList.drop(2)) + pressBack() + // Categories + clickOnViewWithText("${getString(R.string.category_hint)}(2)") + checkCategories(categoryList.drop(1)) + pressBack() + // Tags + clickOnViewWithText("${getString(R.string.record_tag_hint_short)}(3)") + checkTags(tagList.drop(1)) + pressBack() + // Records + clickOnViewWithText("${getString(R.string.shortcut_navigation_records)}(3)") + checkRecords(recordList.drop(1), settingsR.id.rvSettingsPartialRestoreSelectionContainer) + pressBack() + // Activity filters + clickOnViewWithText("${getString(R.string.change_activity_filters_hint)}(1)") + checkActivityFilters(activityFilterList.drop(1)) + pressBack() + // Favourite comments + clickOnViewWithText("${getString(R.string.change_record_favourite_comments_hint_long)}(1)") + checkComments(commentList.drop(1)) + pressBack() + // Favourite icons + clickOnViewWithText("${getString(R.string.change_record_favourite_icons_hint)}(1)") + checkIcons(iconsList.drop(1)) + pressBack() + // Favourite colors + clickOnViewWithText("${getString(R.string.change_record_favourite_colors_hint)}(1)") + checkColors(colorsList.drop(1)) + pressBack() + // Complex rules + clickOnViewWithText("${getString(R.string.settings_complex_rules)}(2)") + checkRules(ruleList.drop(1)) + pressBack() + + // Check consistency + removeFilter(R.string.activity_hint) + removeFilter(R.string.category_hint) + removeFilter(R.string.record_tag_hint_short) + checkViewIsDisplayed(withText(getString(R.string.activity_hint))) + checkViewIsDisplayed(withText(getString(R.string.category_hint))) + checkViewIsDisplayed(withText(getString(R.string.record_tag_hint_short))) + checkViewIsDisplayed(withText("${getString(R.string.shortcut_navigation_records)}(1)")) + checkViewIsDisplayed(withText("${getString(R.string.change_activity_filters_hint)}(1)")) + checkViewIsDisplayed(withText("${getString(R.string.change_record_favourite_comments_hint_long)}(1)")) + checkViewIsDisplayed(withText("${getString(R.string.change_record_favourite_icons_hint)}(1)")) + checkViewIsDisplayed(withText("${getString(R.string.change_record_favourite_colors_hint)}(1)")) + checkViewIsDisplayed(withText("${getString(R.string.settings_complex_rules)}(1)")) + + // Restore only activities + removeFilter(R.string.shortcut_navigation_records) + removeFilter(R.string.change_activity_filters_hint) + removeFilter(R.string.change_record_favourite_comments_hint_long) + removeFilter(R.string.change_record_favourite_icons_hint) + removeFilter(R.string.change_record_favourite_colors_hint) + removeFilter(R.string.settings_complex_rules) + clickOnViewWithText(getString(R.string.activity_hint)) + activityList.get(2).let { clickOnViewWithText(it.name) } + clickOnViewWithText(R.string.duration_dialog_save) + clickOnViewWithText(R.string.backup_options_import) + clickOnViewWithText(R.string.ok) + + // Check message + tryAction { checkViewIsDisplayed(withText(R.string.message_import_complete)) } + clickOnViewWithId(com.google.android.material.R.id.snackbar_text) + + // Check data + NavUtils.openRunningRecordsScreen() + activityList.take(3).forEach { checkViewIsDisplayed(withText(it.name)) } + activityList.last().let { checkViewDoesNotExist(withText(it.name)) } + } + private fun getIconResIdByName(name: String): Int { return iconImageMapper.getAvailableImages(loadSearchHints = false) .values.flatten().first { it.iconName == name }.iconResId diff --git a/app/src/androidTest/java/com/example/util/simpletimetracker/SettingsTest.kt b/app/src/androidTest/java/com/example/util/simpletimetracker/SettingsTest.kt index 4f97df54e..2a72336a1 100644 --- a/app/src/androidTest/java/com/example/util/simpletimetracker/SettingsTest.kt +++ b/app/src/androidTest/java/com/example/util/simpletimetracker/SettingsTest.kt @@ -38,6 +38,7 @@ import com.example.util.simpletimetracker.utils.getMillis import com.example.util.simpletimetracker.utils.longClickOnViewWithId import com.example.util.simpletimetracker.utils.recyclerItemCount import com.example.util.simpletimetracker.utils.tryAction +import com.example.util.simpletimetracker.utils.typeTextIntoView import com.example.util.simpletimetracker.utils.withPluralText import dagger.hilt.android.testing.HiltAndroidTest import kotlinx.coroutines.runBlocking @@ -56,6 +57,7 @@ import com.example.util.simpletimetracker.feature_dialogs.R as dialogsR import com.example.util.simpletimetracker.feature_records.R as recordsR import com.example.util.simpletimetracker.feature_statistics.R as statisticsR import com.example.util.simpletimetracker.feature_statistics_detail.R as statisticsDetailR +import com.example.util.simpletimetracker.feature_tag_selection.R as tagSelectionR @HiltAndroidTest @RunWith(AndroidJUnit4::class) @@ -539,6 +541,30 @@ class SettingsTest : BaseUiTest() { checkViewDoesNotExist(withText(coreR.string.settings_show_notifications_controls)) } + @Test + fun showNotificationEvenWithNoTimers() { + NavUtils.openSettingsScreen() + NavUtils.openSettingsNotifications() + + // Check settings + scrollSettingsRecyclerToText(coreR.string.settings_show_notifications) + checkViewDoesNotExist(withText(coreR.string.settings_show_notification_even_with_no_timers)) + + clickOnSettingsCheckboxBesideText(coreR.string.settings_show_notifications) + scrollSettingsRecyclerToText(coreR.string.settings_show_notification_even_with_no_timers) + checkViewIsDisplayed(withText(coreR.string.settings_show_notification_even_with_no_timers)) + checkCheckboxIsNotChecked(settingsCheckboxBesideText(coreR.string.settings_show_notification_even_with_no_timers)) + clickOnSettingsCheckboxBesideText(coreR.string.settings_show_notification_even_with_no_timers) + checkCheckboxIsChecked(settingsCheckboxBesideText(coreR.string.settings_show_notification_even_with_no_timers)) + clickOnSettingsCheckboxBesideText(coreR.string.settings_show_notification_even_with_no_timers) + checkCheckboxIsNotChecked(settingsCheckboxBesideText(coreR.string.settings_show_notification_even_with_no_timers)) + + // Change settings + scrollSettingsRecyclerToText(coreR.string.settings_show_notifications) + clickOnSettingsCheckboxBesideText(coreR.string.settings_show_notifications) + checkViewDoesNotExist(withText(coreR.string.settings_show_notification_even_with_no_timers)) + } + @Test fun enableEnableDarkMode() { val name1 = "Test1" @@ -1415,6 +1441,8 @@ class SettingsTest : BaseUiTest() { // Add data testUtils.addActivity(name) + + // Started right away Thread.sleep(1000) tryAction { clickOnViewWithText(name) } tryAction { clickOnView(allOf(withId(baseR.id.viewRunningRecordItem), hasDescendant(withText(name)))) } @@ -1478,6 +1506,8 @@ class SettingsTest : BaseUiTest() { // Add data testUtils.addActivity(name) + + // Started right away Thread.sleep(1000) tryAction { clickOnViewWithText(name) } tryAction { clickOnView(allOf(isDescendantOfA(withId(baseR.id.viewRunningRecordItem)), withText(name))) } @@ -1540,6 +1570,8 @@ class SettingsTest : BaseUiTest() { // Add data testUtils.addActivity(name) + + // Started right away Thread.sleep(1000) tryAction { clickOnViewWithText(name) } tryAction { clickOnView(allOf(withId(baseR.id.viewRunningRecordItem), hasDescendant(withText(name)))) } @@ -1548,10 +1580,7 @@ class SettingsTest : BaseUiTest() { NavUtils.openSettingsScreen() NavUtils.openSettingsAdditional() scrollSettingsRecyclerToText(coreR.string.settings_show_record_tag_selection) - checkViewIsNotDisplayed(settingsButtonBesideText(coreR.string.settings_show_record_tag_selection)) - clickOnSettingsCheckboxBesideText(coreR.string.settings_show_record_tag_selection) - checkViewIsDisplayed(settingsButtonBesideText(coreR.string.settings_show_record_tag_selection)) // No tags - started right away NavUtils.openRunningRecordsScreen() @@ -1581,6 +1610,214 @@ class SettingsTest : BaseUiTest() { tryAction { clickOnView(allOf(isDescendantOfA(withId(baseR.id.viewRunningRecordItem)), withText(name))) } } + @Test + fun commentSelection() { + val name = "TypeName" + val comment = "comment" + + // Add data + testUtils.addActivity(name) + + // Started right away + Thread.sleep(1000) + tryAction { clickOnViewWithText(name) } + tryAction { clickOnView(allOf(withId(baseR.id.viewRunningRecordItem), hasDescendant(withText(name)))) } + + // Change setting + NavUtils.openSettingsScreen() + NavUtils.openSettingsAdditional() + scrollSettingsRecyclerToText(coreR.string.settings_show_comment_input) + checkCheckboxIsNotChecked(settingsCheckboxBesideText(coreR.string.settings_show_comment_input)) + clickOnSettingsCheckboxBesideText(coreR.string.settings_show_comment_input) + checkCheckboxIsChecked(settingsCheckboxBesideText(coreR.string.settings_show_comment_input)) + + // Dialog shown + NavUtils.openRunningRecordsScreen() + clickOnViewWithText(name) + tryAction { checkViewIsDisplayed(withId(tagSelectionR.id.inputRecordTagSelectionComment)) } + pressBack() + + // Start without comment + clickOnViewWithText(name) + clickOnViewWithText(coreR.string.duration_dialog_save) + tryAction { checkViewIsNotDisplayed(withId(R.id.tvRunningRecordItemComment)) } + tryAction { clickOnView(allOf(isDescendantOfA(withId(baseR.id.viewRunningRecordItem)), withText(name))) } + + // Start with comment + clickOnViewWithText(name) + tryAction { typeTextIntoView(tagSelectionR.id.etRecordTagSelectionCommentItem, comment) } + clickOnViewWithText(coreR.string.duration_dialog_save) + tryAction { clickOnView(allOf(isDescendantOfA(withId(baseR.id.viewRunningRecordItem)), withText(comment))) } + + // Change setting + NavUtils.openSettingsScreen() + scrollSettingsRecyclerToText(coreR.string.settings_show_comment_input) + checkCheckboxIsChecked(settingsCheckboxBesideText(coreR.string.settings_show_comment_input)) + clickOnSettingsCheckboxBesideText(coreR.string.settings_show_comment_input) + checkCheckboxIsNotChecked(settingsCheckboxBesideText(coreR.string.settings_show_comment_input)) + + // No dialog + NavUtils.openRunningRecordsScreen() + clickOnViewWithText(name) + tryAction { clickOnView(allOf(isDescendantOfA(withId(baseR.id.viewRunningRecordItem)), withText(name))) } + } + + @Test + fun commentSelectionExcludeActivities() { + val name = "TypeName" + + // Add data + testUtils.addActivity(name) + + // Started right away + Thread.sleep(1000) + tryAction { clickOnViewWithText(name) } + tryAction { clickOnView(allOf(withId(baseR.id.viewRunningRecordItem), hasDescendant(withText(name)))) } + + // Change setting + NavUtils.openSettingsScreen() + NavUtils.openSettingsAdditional() + scrollSettingsRecyclerToText(coreR.string.settings_show_comment_input) + clickOnSettingsCheckboxBesideText(coreR.string.settings_show_comment_input) + + // Not excluded - show dialog + NavUtils.openRunningRecordsScreen() + clickOnViewWithText(name) + tryAction { checkViewIsDisplayed(withId(tagSelectionR.id.inputRecordTagSelectionComment)) } + pressBack() + + // Change setting + NavUtils.openSettingsScreen() + scrollSettingsRecyclerToText(coreR.string.settings_show_comment_input) + clickOnSettingsButtonBesideText(coreR.string.settings_show_comment_input) + Thread.sleep(1000) + clickOnViewWithText(name) + clickOnViewWithText(coreR.string.duration_dialog_save) + + // Excluded - no dialog + NavUtils.openRunningRecordsScreen() + clickOnViewWithText(name) + tryAction { clickOnView(allOf(isDescendantOfA(withId(baseR.id.viewRunningRecordItem)), withText(name))) } + } + + @Test + fun tagAndCommentSelection() { + val name = "TypeName" + val tag = "TagGeneral" + val comment = "comment" + val fullName = "$name - $tag" + + // Add data + testUtils.addActivity(name) + testUtils.addRecordTag(tag) + + // Started right away + Thread.sleep(1000) + tryAction { clickOnViewWithText(name) } + tryAction { clickOnView(allOf(withId(baseR.id.viewRunningRecordItem), hasDescendant(withText(name)))) } + + // Change settings + NavUtils.openSettingsScreen() + NavUtils.openSettingsAdditional() + scrollSettingsRecyclerToText(coreR.string.settings_show_record_tag_selection) + clickOnSettingsCheckboxBesideText(coreR.string.settings_show_record_tag_selection) + scrollSettingsRecyclerToText(coreR.string.settings_show_comment_input) + clickOnSettingsCheckboxBesideText(coreR.string.settings_show_comment_input) + + // Dialog shown + NavUtils.openRunningRecordsScreen() + clickOnViewWithText(name) + tryAction { checkViewIsDisplayed(withText(tag)) } + tryAction { checkViewIsDisplayed(withId(tagSelectionR.id.inputRecordTagSelectionComment)) } + pressBack() + + // Start untagged, no comment + clickOnViewWithText(name) + clickOnViewWithText(coreR.string.duration_dialog_save) + tryAction { checkViewIsNotDisplayed(withId(R.id.tvRunningRecordItemComment)) } + tryAction { clickOnView(allOf(isDescendantOfA(withId(baseR.id.viewRunningRecordItem)), withText(name))) } + + // Start untagged, with comment + clickOnViewWithText(name) + tryAction { typeTextIntoView(tagSelectionR.id.etRecordTagSelectionCommentItem, comment) } + clickOnViewWithText(coreR.string.duration_dialog_save) + tryAction { checkViewIsDisplayed(allOf(withId(R.id.tvRunningRecordItemComment), withText(comment))) } + tryAction { clickOnView(allOf(isDescendantOfA(withId(baseR.id.viewRunningRecordItem)), withText(name))) } + + // Start tagged, without comment + clickOnViewWithText(name) + clickOnViewWithText(tag) + clickOnViewWithText(coreR.string.duration_dialog_save) + tryAction { checkViewIsNotDisplayed(withId(R.id.tvRunningRecordItemComment)) } + tryAction { clickOnView(allOf(isDescendantOfA(withId(baseR.id.viewRunningRecordItem)), withText(fullName))) } + + // Start tagged, with comment + clickOnViewWithText(name) + tryAction { typeTextIntoView(tagSelectionR.id.etRecordTagSelectionCommentItem, comment) } + clickOnViewWithText(tag) + clickOnViewWithText(coreR.string.duration_dialog_save) + tryAction { checkViewIsDisplayed(allOf(withId(R.id.tvRunningRecordItemComment), withText(comment))) } + tryAction { clickOnView(allOf(isDescendantOfA(withId(baseR.id.viewRunningRecordItem)), withText(fullName))) } + + // Exclude for tags + NavUtils.openSettingsScreen() + scrollSettingsRecyclerToText(coreR.string.settings_show_record_tag_selection) + clickOnSettingsButtonBesideText(coreR.string.settings_show_record_tag_selection) + Thread.sleep(1000) + clickOnViewWithText(name) + clickOnViewWithText(coreR.string.duration_dialog_save) + + NavUtils.openRunningRecordsScreen() + clickOnViewWithText(name) + tryAction { checkViewDoesNotExist(withText(tag)) } + tryAction { checkViewIsDisplayed(withId(tagSelectionR.id.inputRecordTagSelectionComment)) } + pressBack() + + // Exclude for comments + NavUtils.openSettingsScreen() + scrollSettingsRecyclerToText(coreR.string.settings_show_record_tag_selection) + clickOnSettingsButtonBesideText(coreR.string.settings_show_record_tag_selection) + Thread.sleep(1000) + clickOnViewWithText(name) + clickOnViewWithText(coreR.string.duration_dialog_save) + + scrollSettingsRecyclerToText(coreR.string.settings_show_comment_input) + clickOnSettingsButtonBesideText(coreR.string.settings_show_comment_input) + Thread.sleep(1000) + clickOnViewWithText(name) + clickOnViewWithText(coreR.string.duration_dialog_save) + + NavUtils.openRunningRecordsScreen() + clickOnViewWithText(name) + tryAction { checkViewIsDisplayed(withText(tag)) } + tryAction { checkViewIsNotDisplayed(withId(tagSelectionR.id.inputRecordTagSelectionComment)) } + pressBack() + + // Exclude both + NavUtils.openSettingsScreen() + scrollSettingsRecyclerToText(coreR.string.settings_show_record_tag_selection) + clickOnSettingsButtonBesideText(coreR.string.settings_show_record_tag_selection) + Thread.sleep(1000) + clickOnViewWithText(name) + clickOnViewWithText(coreR.string.duration_dialog_save) + + NavUtils.openRunningRecordsScreen() + clickOnViewWithText(name) + tryAction { clickOnView(allOf(withId(baseR.id.viewRunningRecordItem), hasDescendant(withText(name)))) } + + // Change setting + NavUtils.openSettingsScreen() + scrollSettingsRecyclerToText(coreR.string.settings_show_record_tag_selection) + clickOnSettingsCheckboxBesideText(coreR.string.settings_show_record_tag_selection) + scrollSettingsRecyclerToText(coreR.string.settings_show_comment_input) + clickOnSettingsCheckboxBesideText(coreR.string.settings_show_comment_input) + + // No dialog + NavUtils.openRunningRecordsScreen() + clickOnViewWithText(name) + tryAction { clickOnView(allOf(isDescendantOfA(withId(baseR.id.viewRunningRecordItem)), withText(name))) } + } + @Test fun csvExportSettings() { NavUtils.openSettingsScreen() @@ -1738,6 +1975,40 @@ class SettingsTest : BaseUiTest() { timeEnded = (calendar.timeInMillis + TimeUnit.DAYS.toMillis(1)).formatDateTimeYear() checkViewIsDisplayed(allOf(withId(dialogsR.id.tvCsvExportSettingsTimeStarted), withText(timeStarted))) checkViewIsDisplayed(allOf(withId(dialogsR.id.tvCsvExportSettingsTimeEnded), withText(timeEnded))) + + clickOnViewWithText(R.string.range_custom) + checkViewIsDisplayed(allOf(withId(dialogsR.id.tvCsvExportSettingsTimeStarted), withText(timeStarted))) + checkViewIsDisplayed(allOf(withId(dialogsR.id.tvCsvExportSettingsTimeEnded), withText(timeEnded))) + + // Selected filter saved + pressBack() + clickOnSettingsRecyclerText(coreR.string.settings_export_csv) + checkViewIsDisplayed(allOf(withId(dialogsR.id.tvCsvExportSettingsTimeStarted), withText(timeStarted))) + checkViewIsDisplayed(allOf(withId(dialogsR.id.tvCsvExportSettingsTimeEnded), withText(timeEnded))) + } + + @Test + fun icsExportSettings() { + NavUtils.openSettingsScreen() + NavUtils.openSettingsExportImport() + scrollSettingsRecyclerToText(coreR.string.settings_export_ics) + clickOnSettingsRecyclerText(coreR.string.settings_export_ics) + + // View is set up + checkViewIsDisplayed( + allOf( + withId(dialogsR.id.etCsvExportSettingsFileName), + withText("stt_events_{date}.ics"), + ), + ) + val currentTime = Calendar.getInstance().apply { + timeInMillis = System.currentTimeMillis() + setToStartOfDay() + }.timeInMillis + val timeStarted = currentTime.formatDateTimeYear() + val timeEnded = (currentTime + TimeUnit.DAYS.toMillis(1)).formatDateTimeYear() + checkViewIsDisplayed(allOf(withId(dialogsR.id.tvCsvExportSettingsTimeStarted), withText(timeStarted))) + checkViewIsDisplayed(allOf(withId(dialogsR.id.tvCsvExportSettingsTimeEnded), withText(timeEnded))) } @Test diff --git a/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordRepeatInteractor.kt b/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordRepeatInteractor.kt index 6fa7148f2..e2233cd1b 100644 --- a/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordRepeatInteractor.kt +++ b/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordRepeatInteractor.kt @@ -12,6 +12,7 @@ import com.example.util.simpletimetracker.navigation.params.notification.SnackBa import com.example.util.simpletimetracker.navigation.params.notification.ToastParams import javax.inject.Inject +// Repeats previous record, if any. class RecordRepeatInteractor @Inject constructor( private val recordInteractor: RecordInteractor, private val runningRecordInteractor: RunningRecordInteractor, 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 c12077899..53678566a 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 @@ -7,6 +7,9 @@ import com.example.util.simpletimetracker.domain.interactor.ActivityFilterIntera import com.example.util.simpletimetracker.domain.interactor.CategoryInteractor import com.example.util.simpletimetracker.domain.interactor.ClearDataInteractor import com.example.util.simpletimetracker.domain.interactor.ComplexRuleInteractor +import com.example.util.simpletimetracker.domain.interactor.FavouriteColorInteractor +import com.example.util.simpletimetracker.domain.interactor.FavouriteCommentInteractor +import com.example.util.simpletimetracker.domain.interactor.FavouriteIconInteractor import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor import com.example.util.simpletimetracker.domain.interactor.RecordInteractor import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor @@ -21,6 +24,9 @@ import com.example.util.simpletimetracker.domain.model.AppColor import com.example.util.simpletimetracker.domain.model.Category import com.example.util.simpletimetracker.domain.model.ComplexRule import com.example.util.simpletimetracker.domain.model.DayOfWeek +import com.example.util.simpletimetracker.domain.model.FavouriteColor +import com.example.util.simpletimetracker.domain.model.FavouriteComment +import com.example.util.simpletimetracker.domain.model.FavouriteIcon import com.example.util.simpletimetracker.domain.model.Record import com.example.util.simpletimetracker.domain.model.RecordTag import com.example.util.simpletimetracker.domain.model.RecordType @@ -41,6 +47,9 @@ class TestUtils @Inject constructor( private val recordTypeToDefaultTagInteractor: RecordTypeToDefaultTagInteractor, private val activityFilterInteractor: ActivityFilterInteractor, private val recordTypeGoalInteractor: RecordTypeGoalInteractor, + private val favouriteCommentInteractor: FavouriteCommentInteractor, + private val favouriteIconInteractor: FavouriteIconInteractor, + private val favouriteColorInteractor: FavouriteColorInteractor, private val complexRuleInteractor: ComplexRuleInteractor, private val prefsInteractor: PrefsInteractor, private val iconImageMapper: IconImageMapper, @@ -185,6 +194,7 @@ class TestUtils @Inject constructor( typeName: String? = null, archived: Boolean = false, color: Int? = null, + icon: Int? = null, note: String = "", defaultTypes: List = emptyList(), ) = runBlocking { @@ -194,9 +204,14 @@ class TestUtils @Inject constructor( val colorId = colors.indexOf(color).takeUnless { it == -1 } ?: (0..colors.size).random() + val icons = iconImageMapper + .getAvailableImages(loadSearchHints = false).values + .flatten().associateBy { it.iconName }.mapValues { it.value.iconResId } + val iconId = icons.filterValues { it == icon }.keys.firstOrNull() + val data = RecordTag( name = tagName, - icon = "", + icon = iconId.orEmpty(), color = AppColor(colorId = colorId, colorInt = ""), iconColorSource = type?.id.orZero(), note = note, @@ -255,6 +270,24 @@ class TestUtils @Inject constructor( activityFilterInteractor.add(data) } + fun addFavouriteComment( + text: String, + ) = runBlocking { + favouriteCommentInteractor.add(FavouriteComment(comment = text)) + } + + fun addFavouriteIcon( + text: String, + ) = runBlocking { + favouriteIconInteractor.add(FavouriteIcon(icon = text)) + } + + fun addFavouriteColor( + colorInt: Int, + ) = runBlocking { + favouriteColorInteractor.add(FavouriteColor(colorInt = colorInt.toString())) + } + fun addComplexRule( action: ComplexRule.Action, assignTagNames: List = emptyList(), diff --git a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/BackupPartialRepoImpl.kt b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/BackupPartialRepoImpl.kt index 9a6022920..89dade1af 100644 --- a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/BackupPartialRepoImpl.kt +++ b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/BackupPartialRepoImpl.kt @@ -353,8 +353,13 @@ class BackupPartialRepoImpl @Inject constructor( }.list val newActivityFilters = activityFilters.map { item -> - val newTypeIds = item.selectedIds - .mapNotNull { originalTypeIdToExistingId[it] } + val newTypeIds = item.selectedIds.mapNotNull { + if (item.type is ActivityFilter.Type.Activity) { + originalTypeIdToExistingId[it] + } else { + it + } + } item.copy( selectedIds = newTypeIds, ) diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordActionRepeatMediator.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordActionRepeatMediator.kt index afa456d45..ae04c69eb 100644 --- a/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordActionRepeatMediator.kt +++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordActionRepeatMediator.kt @@ -2,6 +2,7 @@ package com.example.util.simpletimetracker.domain.interactor import javax.inject.Inject +// Repeats concrete record. class RecordActionRepeatMediator @Inject constructor( private val runningRecordInteractor: RunningRecordInteractor, private val addRunningRecordMediator: AddRunningRecordMediator, diff --git a/features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/recordType/interactor/NotificationTypeInteractorImpl.kt b/features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/recordType/interactor/NotificationTypeInteractorImpl.kt index 3b10c9659..92e22acf0 100644 --- a/features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/recordType/interactor/NotificationTypeInteractorImpl.kt +++ b/features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/recordType/interactor/NotificationTypeInteractorImpl.kt @@ -47,7 +47,7 @@ class NotificationTypeInteractorImpl @Inject constructor( private val getNotificationActivitySwitchControlsInteractor: GetNotificationActivitySwitchControlsInteractor, ) : NotificationTypeInteractor { - // TODO SWITCH merge with update function? + // TODO merge with update function? override suspend fun checkAndShow( typeId: Long, typesShift: Int,