A fork of Simple Time Tracker with WearOS support. (Now integrated upstream)
0

Configure Feed

Select the types of activity you want to include in your feed.

random fixes

razeeman (May 1, 2023, 12:17 PM +0300) f187b709 35946bc2

+116 -67
+1 -1
features/feature_records_filter/src/main/res/layout/item_records_filter_comment.xml
··· 1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 <com.google.android.material.textfield.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 3 android:id="@+id/inputChangeRecordComment" 4 - style="@style/ChangeItemInputField" 4 + style="@style/ChangeItemInputFieldDialog" 5 5 android:layout_width="match_parent" 6 6 android:layout_height="wrap_content" 7 7 android:layout_marginHorizontal="20dp">
+5 -6
features/feature_widget/src/main/res/layout/widget_quick_settings_view_layout.xml
··· 36 36 android:layout_width="match_parent" 37 37 android:layout_height="wrap_content" 38 38 android:orientation="horizontal" 39 - app:layout_constraintGuide_percent="0.5" /> 39 + app:layout_constraintGuide_percent="0.45" /> 40 40 41 41 <androidx.appcompat.widget.AppCompatTextView 42 42 android:id="@+id/tvWidgetQuickSettingsName" 43 43 android:layout_width="match_parent" 44 44 android:layout_height="0dp" 45 45 android:layout_marginHorizontal="4dp" 46 - android:layout_marginTop="4dp" 47 - android:layout_marginBottom="4dp" 46 + android:layout_marginBottom="8dp" 48 47 android:ellipsize="end" 49 48 android:gravity="center" 50 - android:maxLines="2" 49 + android:maxLines="3" 51 50 android:textColor="@color/widget_universal_empty_color" 52 51 android:textStyle="bold" 53 52 app:autoSizeMaxTextSize="14sp" 54 - app:autoSizeMinTextSize="1sp" 53 + app:autoSizeMinTextSize="12sp" 55 54 app:autoSizeTextType="uniform" 56 55 app:layout_constraintBottom_toBottomOf="parent" 57 56 app:layout_constraintTop_toBottomOf="@id/guideWidgetQuickSettings" 58 - tools:text="@string/settings_allow_multitasking" /> 57 + tools:text="Show record tag selection show record tag selection show record tag selection" /> 59 58 60 59 </androidx.constraintlayout.widget.ConstraintLayout> 61 60
+4 -4
app/src/androidTest/java/com/example/util/simpletimetracker/IconTest.kt
··· 284 284 285 285 if (category == iconImageMapper.getAvailableCategories().last()) { 286 286 onView( 287 - withId(changeRecordTypeR.id.rvChangeRecordTypeIcon)).perform(collapseToolbar() 288 - ) 287 + withId(changeRecordTypeR.id.rvChangeRecordTypeIcon) 288 + ).perform(collapseToolbar()) 289 289 onView( 290 - withId(changeRecordTypeR.id.rvChangeRecordTypeIcon)).perform(swipeUp(50) 291 - ) 290 + withId(changeRecordTypeR.id.rvChangeRecordTypeIcon) 291 + ).perform(swipeUp(50)) 292 292 } 293 293 294 294 // Check category hint
+8 -12
app/src/androidTest/java/com/example/util/simpletimetracker/RecordActionsContinueTest.kt
··· 114 114 isCompletelyDisplayed() 115 115 ) 116 116 ) 117 - onView( 118 - allOf(withId(recordsR.id.rvRecordsList), isCompletelyDisplayed())).check(recyclerItemCount(2) 119 - ) 117 + onView(allOf(withId(recordsR.id.rvRecordsList), isCompletelyDisplayed())) 118 + .check(recyclerItemCount(2)) 120 119 clickOnViewWithText(coreR.string.untracked_time_name) 121 120 122 121 // Continue untracked doesn't work ··· 142 141 ) 143 142 ) 144 143 checkViewDoesNotExist(allOf(withText(coreR.string.untracked_time_name), isCompletelyDisplayed())) 145 - onView( 146 - allOf(withId(recordsR.id.rvRecordsList), isCompletelyDisplayed())).check(recyclerItemCount(2) 147 - ) 144 + onView(allOf(withId(recordsR.id.rvRecordsList), isCompletelyDisplayed())) 145 + .check(recyclerItemCount(2)) 148 146 149 147 NavUtils.openRunningRecordsScreen() 150 148 checkViewIsDisplayed( ··· 172 170 NavUtils.openRecordsScreen() 173 171 174 172 // Open add new record 175 - onView( 176 - allOf(withId(recordsR.id.rvRecordsList), isCompletelyDisplayed())).check(recyclerItemCount(1) 177 - ) 173 + onView(allOf(withId(recordsR.id.rvRecordsList), isCompletelyDisplayed())) 174 + .check(recyclerItemCount(1)) 178 175 clickOnViewWithId(recordsR.id.btnRecordAdd) 179 176 180 177 // Continue untracked doesn't work ··· 206 203 isCompletelyDisplayed() 207 204 ) 208 205 ) 209 - onView( 210 - allOf(withId(recordsR.id.rvRecordsList), isCompletelyDisplayed())).check(recyclerItemCount(2) 211 - ) 206 + onView(allOf(withId(recordsR.id.rvRecordsList), isCompletelyDisplayed())) 207 + .check(recyclerItemCount(2)) 212 208 213 209 NavUtils.openRunningRecordsScreen() 214 210 checkViewIsDisplayed(
+2 -2
core/src/main/java/com/example/util/simpletimetracker/core/interactor/ChartFilterViewDataInteractor.kt
··· 93 93 categoryViewDataMapper.mapRecordTag( 94 94 tag = tag, 95 95 type = typesMap[tag.typeId], 96 - isDarkTheme = tag.id in recordTagsFiltered, 97 - isFiltered = isDarkTheme 96 + isDarkTheme = isDarkTheme, 97 + isFiltered = tag.id in recordTagsFiltered, 98 98 ) 99 99 } 100 100 .takeUnless { it.isEmpty() }
+8 -1
core/src/main/java/com/example/util/simpletimetracker/core/mapper/RangeMapper.kt
··· 105 105 rangeStart: Long, 106 106 rangeEnd: Long, 107 107 ): Record { 108 - return if (!record.isInRange(rangeStart, rangeEnd)) { 108 + return if (!record.isCompletelyRange(rangeStart, rangeEnd)) { 109 109 record.copy( 110 110 timeStarted = max(record.timeStarted, rangeStart), 111 111 timeEnded = min(record.timeEnded, rangeEnd) ··· 157 157 rangeEnd: Long, 158 158 ): Boolean { 159 159 return this.timeStarted < rangeEnd && this.timeEnded > rangeStart 160 + } 161 + 162 + private fun Record.isCompletelyRange( 163 + rangeStart: Long, 164 + rangeEnd: Long, 165 + ): Boolean { 166 + return this.timeStarted >= rangeStart && this.timeEnded <= rangeEnd 160 167 } 161 168 162 169 companion object {
+3 -3
features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/viewModel/DataEditViewModel.kt
··· 151 151 fun onDeleteRecordsClick() { 152 152 if (deleteState is DataEditDeleteRecordsState.Disabled) { 153 153 deleteState = DataEditDeleteRecordsState.Enabled 154 - } else { 155 - deleteState = DataEditDeleteRecordsState.Disabled 156 154 157 155 typeState = DataEditChangeActivityState.Disabled 158 156 commentState = DataEditChangeCommentState.Disabled ··· 163 161 updateChangeCommentState() 164 162 updateAddTagState() 165 163 updateRemoveTagState() 164 + } else { 165 + deleteState = DataEditDeleteRecordsState.Disabled 166 166 } 167 167 updateDeleteRecordsState() 168 168 updateChangeButtonState() ··· 207 207 StandardDialogParams( 208 208 tag = ALERT_DIALOG_TAG, 209 209 message = resourceRepo.getString(R.string.archive_deletion_alert), 210 - btnPositive = resourceRepo.getString(R.string.archive_dialog_delete), 210 + btnPositive = resourceRepo.getString(R.string.data_edit_button_change), 211 211 btnNegative = resourceRepo.getString(R.string.cancel) 212 212 ) 213 213 )
+4 -12
features/feature_statistics/src/main/java/com/example/util/simpletimetracker/feature_statistics/interactor/StatisticsViewDataInteractor.kt
··· 7 7 import com.example.util.simpletimetracker.domain.UNCATEGORIZED_ITEM_ID 8 8 import com.example.util.simpletimetracker.domain.UNTRACKED_ITEM_ID 9 9 import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor 10 - import com.example.util.simpletimetracker.domain.interactor.RecordTypeCategoryInteractor 11 10 import com.example.util.simpletimetracker.domain.interactor.RecordTypeInteractor 12 11 import com.example.util.simpletimetracker.domain.model.ChartFilterType 13 12 import com.example.util.simpletimetracker.domain.model.RangeLength 14 13 import com.example.util.simpletimetracker.domain.model.RecordType 15 - import com.example.util.simpletimetracker.domain.model.RecordTypeCategory 16 14 import com.example.util.simpletimetracker.domain.model.RecordsFilter 17 15 import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType 18 16 import com.example.util.simpletimetracker.feature_base_adapter.divider.DividerViewData ··· 24 22 25 23 class StatisticsViewDataInteractor @Inject constructor( 26 24 private val recordTypeInteractor: RecordTypeInteractor, 27 - private val recordTypeCategoryInteractor: RecordTypeCategoryInteractor, 28 25 private val statisticsMediator: StatisticsMediator, 29 26 private val statisticsChartViewDataInteractor: StatisticsChartViewDataInteractor, 30 27 private val prefsInteractor: PrefsInteractor, ··· 33 30 private val colorMapper: ColorMapper, 34 31 ) { 35 32 36 - suspend fun mapFilter( 33 + fun mapFilter( 37 34 filterType: ChartFilterType, 38 35 selectedId: Long, 39 36 ): RecordsFilter { 40 37 if (selectedId == UNCATEGORIZED_ITEM_ID) { 41 38 when (filterType) { 42 39 ChartFilterType.CATEGORY -> { 43 - val typesInCategories = recordTypeCategoryInteractor.getAll() 44 - .map(RecordTypeCategory::recordTypeId) 45 - .distinct() 46 - val typesNotInCategories = recordTypeInteractor.getAll() 47 - .filterNot { it.id in typesInCategories } 48 - .map(RecordType::id) 49 - 50 - return RecordsFilter.Activity(typesNotInCategories) 40 + return RecordsFilter.CategoryItem.Uncategorized 41 + .let(::listOf) 42 + .let(RecordsFilter::Category) 51 43 } 52 44 ChartFilterType.RECORD_TAG -> { 53 45 return RecordsFilter.TagItem.Untagged
+22 -11
features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/interactor/StatisticsDetailPreviewInteractor.kt
··· 1 1 package com.example.util.simpletimetracker.feature_statistics_detail.interactor 2 2 3 3 import com.example.util.simpletimetracker.core.interactor.RecordFilterInteractor 4 - import com.example.util.simpletimetracker.domain.extension.getCategoryIds 4 + import com.example.util.simpletimetracker.domain.extension.getCategoryItems 5 5 import com.example.util.simpletimetracker.domain.extension.getSelectedTags 6 6 import com.example.util.simpletimetracker.domain.extension.getTypeIds 7 7 import com.example.util.simpletimetracker.domain.extension.hasActivityFilter 8 8 import com.example.util.simpletimetracker.domain.extension.hasCategoryFilter 9 9 import com.example.util.simpletimetracker.domain.extension.hasSelectedTagsFilter 10 - import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType 11 10 import com.example.util.simpletimetracker.domain.interactor.CategoryInteractor 12 11 import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor 13 12 import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor 14 13 import com.example.util.simpletimetracker.domain.interactor.RecordTypeInteractor 14 + import com.example.util.simpletimetracker.domain.model.Category 15 15 import com.example.util.simpletimetracker.domain.model.RecordTag 16 16 import com.example.util.simpletimetracker.domain.model.RecordType 17 17 import com.example.util.simpletimetracker.domain.model.RecordsFilter 18 + import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType 18 19 import com.example.util.simpletimetracker.feature_statistics_detail.mapper.StatisticsDetailViewDataMapper 19 20 import com.example.util.simpletimetracker.feature_statistics_detail.viewData.StatisticsDetailPreviewCompareViewData 20 21 import javax.inject.Inject ··· 55 56 mapActivities(selectedIds) 56 57 } 57 58 filterParams.hasCategoryFilter() -> { 58 - val selectedIds = filterParams.getCategoryIds() 59 - categoryInteractor.getAll() 60 - .filter { it.id in selectedIds } 61 - .map { category -> 62 - statisticsDetailViewDataMapper.mapToPreview( 63 - category = category, 64 - isDarkTheme = isDarkTheme, 65 - isForComparison = isForComparison, 66 - ) 59 + val selectedCategories = filterParams.getCategoryItems() 60 + val categories = categoryInteractor.getAll().associateBy(Category::id) 61 + selectedCategories.mapNotNull { 62 + when (it) { 63 + is RecordsFilter.CategoryItem.Categorized -> { 64 + val category = categories[it.categoryId] ?: return@mapNotNull null 65 + statisticsDetailViewDataMapper.mapToCategorizedPreview( 66 + category = category, 67 + isDarkTheme = isDarkTheme, 68 + isForComparison = isForComparison, 69 + ) 70 + } 71 + is RecordsFilter.CategoryItem.Uncategorized -> { 72 + statisticsDetailViewDataMapper.mapToUncategorizedPreview( 73 + isDarkTheme = isDarkTheme, 74 + isForComparison = isForComparison, 75 + ) 76 + } 67 77 } 78 + } 68 79 } 69 80 filterParams.hasSelectedTagsFilter() -> { 70 81 val selectedTags = filterParams.getSelectedTags()
+3 -2
features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/interactor/StatisticsDetailStatsInteractor.kt
··· 5 5 import com.example.util.simpletimetracker.core.mapper.RangeMapper 6 6 import com.example.util.simpletimetracker.core.mapper.TimeMapper 7 7 import com.example.util.simpletimetracker.core.repo.ResourceRepo 8 + import com.example.util.simpletimetracker.domain.UNCATEGORIZED_ITEM_ID 8 9 import com.example.util.simpletimetracker.domain.extension.orZero 9 10 import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor 10 11 import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor ··· 362 363 tags.getOrPut(tagId) { mutableListOf() }.add(record) 363 364 } 364 365 if (record.tagIds.isEmpty()) { 365 - tags.getOrPut(0) { mutableListOf() }.add(record) 366 + tags.getOrPut(UNCATEGORIZED_ITEM_ID) { mutableListOf() }.add(record) 366 367 } 367 368 } 368 369 ··· 386 387 icon = type?.icon 387 388 ?.let(iconMapper::mapIcon) 388 389 ?: tag?.run { RecordTypeIcon.Image(0) } 389 - ?: RecordTypeIcon.Image(R.drawable.unknown), 390 + ?: RecordTypeIcon.Image(R.drawable.untagged), 390 391 color = type?.color?.let { colorMapper.mapToColorInt(it, isDarkTheme) } 391 392 ?: tag?.color?.let { colorMapper.mapToColorInt(it, isDarkTheme) } 392 393 ?: colorMapper.toUntrackedColor(isDarkTheme),
+23 -1
features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/mapper/StatisticsDetailViewDataMapper.kt
··· 60 60 ) 61 61 } 62 62 63 - fun mapToPreview( 63 + fun mapToCategorizedPreview( 64 64 category: Category, 65 65 isDarkTheme: Boolean, 66 66 isForComparison: Boolean, ··· 76 76 iconId = null, 77 77 color = category.color 78 78 .let { colorMapper.mapToColorInt(it, isDarkTheme) }, 79 + ) 80 + } 81 + 82 + fun mapToUncategorizedPreview( 83 + isDarkTheme: Boolean, 84 + isForComparison: Boolean, 85 + ): StatisticsDetailPreviewViewData { 86 + val item = categoryViewDataMapper.mapToUncategorizedItem( 87 + isDarkTheme = isDarkTheme, 88 + isFiltered = false 89 + ) 90 + 91 + return StatisticsDetailPreviewViewData( 92 + id = item.id, 93 + type = if (isForComparison) { 94 + StatisticsDetailPreviewViewData.Type.COMPARISON 95 + } else { 96 + StatisticsDetailPreviewViewData.Type.FILTER 97 + }, 98 + name = item.name, 99 + iconId = RecordTypeIcon.Image(R.drawable.untagged), 100 + color = item.color, 79 101 ) 80 102 } 81 103
+3 -1
features/feature_dialogs/src/main/java/com/example/util/simpletimetracker/feature_dialogs/chartFilter/viewModel/ChartFilterViewModel.kt
··· 10 10 import com.example.util.simpletimetracker.core.mapper.ChartFilterViewDataMapper 11 11 import com.example.util.simpletimetracker.core.view.buttonsRowView.ButtonsRowViewData 12 12 import com.example.util.simpletimetracker.core.viewData.ChartFilterTypeViewData 13 + import com.example.util.simpletimetracker.domain.UNCATEGORIZED_ITEM_ID 13 14 import com.example.util.simpletimetracker.domain.UNTRACKED_ITEM_ID 14 15 import com.example.util.simpletimetracker.domain.interactor.CategoryInteractor 15 16 import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor ··· 126 127 ChartFilterType.CATEGORY -> { 127 128 getCategoriesCache().map { it.id }.let(categoryIdsFiltered::addAll) 128 129 categoryIdsFiltered.add(UNTRACKED_ITEM_ID) 130 + categoryIdsFiltered.add(UNCATEGORIZED_ITEM_ID) 129 131 prefsInteractor.setFilteredCategories(categoryIdsFiltered) 130 132 } 131 133 ChartFilterType.RECORD_TAG -> { 132 134 getTagsCache().map { it.id }.let(recordTagsFiltered::addAll) 133 - // TODO add untagged 134 135 recordTagsFiltered.add(UNTRACKED_ITEM_ID) 136 + recordTagsFiltered.add(UNCATEGORIZED_ITEM_ID) 135 137 prefsInteractor.setFilteredTags(recordTagsFiltered) 136 138 } 137 139 }
+30 -11
features/feature_widget/src/main/java/com/example/util/simpletimetracker/feature_widget/statistics/settings/WidgetStatisticsSettingsViewModel.kt
··· 13 13 import com.example.util.simpletimetracker.core.viewData.ChartFilterTypeViewData 14 14 import com.example.util.simpletimetracker.core.viewData.RangeViewData 15 15 import com.example.util.simpletimetracker.core.viewData.RangesViewData 16 + import com.example.util.simpletimetracker.domain.UNCATEGORIZED_ITEM_ID 16 17 import com.example.util.simpletimetracker.domain.UNTRACKED_ITEM_ID 17 18 import com.example.util.simpletimetracker.domain.interactor.CategoryInteractor 18 19 import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor ··· 121 122 } 122 123 123 124 fun onCategoryClick(item: CategoryViewData) { 124 - viewModelScope.launch { 125 - val oldIds = widgetData.filteredCategories.toMutableList() 126 - widgetData = widgetData.copy( 127 - filteredCategories = oldIds.apply { addOrRemove(item.id) }.toSet() 128 - ) 129 - updateCategoriesViewData() 125 + when (item) { 126 + is CategoryViewData.Category -> { 127 + val oldIds = widgetData.filteredCategories.toMutableList() 128 + widgetData = widgetData.copy( 129 + filteredCategories = oldIds.apply { addOrRemove(item.id) }.toSet() 130 + ) 131 + updateCategoriesViewData() 132 + } 133 + is CategoryViewData.Record -> { 134 + val oldIds = widgetData.filteredTags.toMutableList() 135 + widgetData = widgetData.copy( 136 + filteredTags = oldIds.apply { addOrRemove(item.id) }.toSet() 137 + ) 138 + updateTagsViewData() 139 + } 130 140 } 131 141 } 132 142 ··· 152 162 widgetData = when (widgetData.chartFilterType) { 153 163 ChartFilterType.ACTIVITY -> { 154 164 widgetData.copy( 155 - filteredTypes = (getTypesCache().map(RecordType::id) + UNTRACKED_ITEM_ID) 156 - .toSet() 165 + filteredTypes = ( 166 + getTypesCache().map(RecordType::id) + 167 + UNTRACKED_ITEM_ID 168 + ).toSet() 157 169 ) 158 170 } 159 171 ChartFilterType.CATEGORY -> { 160 172 widgetData.copy( 161 - filteredTypes = (getCategoriesCache().map(Category::id) + UNTRACKED_ITEM_ID) 162 - .toSet() 173 + filteredCategories = ( 174 + getCategoriesCache().map(Category::id) + 175 + UNTRACKED_ITEM_ID + 176 + UNCATEGORIZED_ITEM_ID 177 + ).toSet() 163 178 ) 164 179 } 165 180 ChartFilterType.RECORD_TAG -> { 166 181 widgetData.copy( 167 - filteredTags = (getTagsCache().map(RecordTag::id) + UNTRACKED_ITEM_ID) 182 + filteredTags = ( 183 + getTagsCache().map(RecordTag::id) + 184 + UNTRACKED_ITEM_ID + 185 + UNCATEGORIZED_ITEM_ID 186 + ) 168 187 .toSet() 169 188 ) 170 189 }