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.

add goal checkmark on type notification

razeeman (Oct 29, 2023, 12:46 PM +0300) 49f6336a 020a9152

+191 -51
+10 -1
features/feature_notification/src/main/res/layout/notification_icon_view_layout.xml
··· 5 5 android:layout_height="match_parent" 6 6 tools:layout_height="@dimen/notification_icon_size" 7 7 tools:layout_width="@dimen/notification_icon_size" 8 - tools:parentTag="android.widget.FrameLayout"> 8 + tools:parentTag="android.widget.RelativeLayout"> 9 9 10 10 <androidx.appcompat.widget.AppCompatImageView 11 11 android:id="@+id/ivNotificationIconBackground" ··· 19 19 android:layout_width="match_parent" 20 20 android:layout_height="match_parent" 21 21 android:layout_margin="8dp" /> 22 + 23 + <com.example.util.simpletimetracker.feature_views.GoalCheckmarkView 24 + android:id="@+id/viewNotificationIconCheckmark" 25 + android:layout_width="14dp" 26 + android:layout_height="14dp" 27 + android:layout_alignParentEnd="true" 28 + android:elevation="@dimen/record_type_card_elevation" 29 + tools:itemIsChecked="true" 30 + tools:itemWithCheck="true" /> 22 31 23 32 </merge>
+2
features/feature_notification/src/main/res/values/attrs.xml
··· 4 4 <declare-styleable name="NotificationIconView"> 5 5 <attr name="itemColor" format="color" /> 6 6 <attr name="itemIcon" format="reference" /> 7 + <attr name="itemWithCheck" /> 8 + <attr name="itemIsChecked" /> 7 9 </declare-styleable> 8 10 9 11 </resources>
+27
features/feature_views/src/main/res/layout/goal_checkmark_view_layout.xml
··· 1 + <?xml version="1.0" encoding="utf-8"?> 2 + <merge xmlns:android="http://schemas.android.com/apk/res/android" 3 + xmlns:app="http://schemas.android.com/apk/res-auto" 4 + xmlns:tools="http://schemas.android.com/tools" 5 + tools:layout_height="20dp" 6 + tools:layout_width="20dp" 7 + tools:parentTag="android.widget.FrameLayout"> 8 + 9 + <androidx.appcompat.widget.AppCompatImageView 10 + android:id="@+id/ivGoalCheckmarkItemCheckOutline" 11 + android:layout_width="match_parent" 12 + android:layout_height="match_parent" 13 + android:tint="?colorSecondary" 14 + android:visibility="gone" 15 + app:srcCompat="@drawable/record_type_check_unmarked" 16 + tools:visibility="visible" /> 17 + 18 + <androidx.appcompat.widget.AppCompatImageView 19 + android:id="@+id/ivGoalCheckmarkItemCheck" 20 + android:layout_width="match_parent" 21 + android:layout_height="match_parent" 22 + android:tint="@color/white" 23 + android:visibility="gone" 24 + app:srcCompat="@drawable/record_type_check_mark" 25 + tools:visibility="visible" /> 26 + 27 + </merge>
+4 -20
features/feature_views/src/main/res/layout/record_type_view_layout.xml
··· 19 19 20 20 </androidx.cardview.widget.CardView> 21 21 22 - <androidx.appcompat.widget.AppCompatImageView 23 - android:id="@+id/ivRecordTypeItemCheckOutline" 22 + <com.example.util.simpletimetracker.feature_views.GoalCheckmarkView 23 + android:id="@+id/viewRecordTypeItemCheckmark" 24 24 android:layout_width="20dp" 25 25 android:layout_height="20dp" 26 26 android:layout_alignParentTop="true" 27 27 android:layout_alignParentEnd="true" 28 28 android:elevation="@dimen/record_type_card_elevation" 29 - android:tint="?colorSecondary" 30 - android:visibility="gone" 31 - app:srcCompat="@drawable/record_type_check_unmarked" 32 - tools:visibility="visible" /> 33 - 34 - <androidx.appcompat.widget.AppCompatImageView 35 - android:id="@+id/ivRecordTypeItemCheck" 36 - android:layout_width="20dp" 37 - android:layout_height="20dp" 38 - android:layout_alignStart="@id/ivRecordTypeItemCheckOutline" 39 - android:layout_alignTop="@id/ivRecordTypeItemCheckOutline" 40 - android:layout_alignEnd="@id/ivRecordTypeItemCheckOutline" 41 - android:layout_alignBottom="@id/ivRecordTypeItemCheckOutline" 42 - android:elevation="@dimen/record_type_card_elevation" 43 - android:tint="@color/white" 44 - android:visibility="gone" 45 - app:srcCompat="@drawable/record_type_check_mark" 46 - tools:visibility="visible" /> 29 + tools:itemIsChecked="true" 30 + tools:itemWithCheck="true" /> 47 31 48 32 </merge>
+7 -2
features/feature_views/src/main/res/values/attrs.xml
··· 38 38 <attr name="itemIconAlpha" format="float" /> 39 39 </declare-styleable> 40 40 41 + <declare-styleable name="GoalCheckmarkView"> 42 + <attr name="itemWithCheck" format="boolean" /> 43 + <attr name="itemIsChecked" format="boolean" /> 44 + </declare-styleable> 45 + 41 46 <declare-styleable name="RecordTypeView"> 42 47 <attr name="itemIcon" /> 43 48 <attr name="itemIconText" /> ··· 46 51 <attr name="itemName" format="string" /> 47 52 <attr name="itemColor" format="color" /> 48 53 <attr name="itemIsRow" format="boolean" /> 49 - <attr name="itemWithCheck" format="boolean" /> 50 - <attr name="itemIsChecked" format="boolean" /> 54 + <attr name="itemWithCheck" /> 55 + <attr name="itemIsChecked" /> 51 56 </declare-styleable> 52 57 53 58 <declare-styleable name="RecordView">
+60
features/feature_views/src/main/java/com/example/util/simpletimetracker/feature_views/GoalCheckmarkView.kt
··· 1 + package com.example.util.simpletimetracker.feature_views 2 + 3 + import android.content.Context 4 + import android.util.AttributeSet 5 + import android.view.LayoutInflater 6 + import android.widget.FrameLayout 7 + import androidx.core.view.isVisible 8 + import com.example.util.simpletimetracker.feature_views.databinding.GoalCheckmarkViewLayoutBinding 9 + 10 + class GoalCheckmarkView @JvmOverloads constructor( 11 + context: Context, 12 + attrs: AttributeSet? = null, 13 + defStyleAttr: Int = 0, 14 + ) : FrameLayout( 15 + context, 16 + attrs, 17 + defStyleAttr, 18 + ) { 19 + 20 + private val binding: GoalCheckmarkViewLayoutBinding = GoalCheckmarkViewLayoutBinding 21 + .inflate(LayoutInflater.from(context), this) 22 + 23 + init { 24 + 25 + context.obtainStyledAttributes(attrs, R.styleable.GoalCheckmarkView, defStyleAttr, 0) 26 + .run { 27 + if (hasValue(R.styleable.GoalCheckmarkView_itemWithCheck)) { 28 + itemWithCheck = getBoolean(R.styleable.GoalCheckmarkView_itemWithCheck, false) 29 + } 30 + 31 + if (hasValue(R.styleable.GoalCheckmarkView_itemIsChecked)) { 32 + itemIsChecked = getBoolean(R.styleable.GoalCheckmarkView_itemIsChecked, false) 33 + } 34 + 35 + recycle() 36 + } 37 + } 38 + 39 + var itemWithCheck: Boolean = false 40 + set(value) { 41 + field = value 42 + setCheckmark() 43 + } 44 + 45 + var itemIsChecked: Boolean = false 46 + set(value) { 47 + field = value 48 + setCheckmark() 49 + } 50 + 51 + private fun setCheckmark() = with(binding) { 52 + if (itemWithCheck) { 53 + ivGoalCheckmarkItemCheckOutline.isVisible = true 54 + ivGoalCheckmarkItemCheck.isVisible = itemIsChecked 55 + } else { 56 + ivGoalCheckmarkItemCheckOutline.isVisible = false 57 + ivGoalCheckmarkItemCheck.isVisible = false 58 + } 59 + } 60 + }
+4 -16
features/feature_views/src/main/java/com/example/util/simpletimetracker/feature_views/RecordTypeView.kt
··· 6 6 import android.view.Gravity 7 7 import android.view.LayoutInflater 8 8 import android.widget.RelativeLayout 9 - import androidx.appcompat.widget.AppCompatImageView 10 9 import androidx.cardview.widget.CardView 11 10 import androidx.constraintlayout.widget.ConstraintSet 12 - import androidx.core.view.isVisible 13 11 import com.example.util.simpletimetracker.feature_views.databinding.RecordTypeViewLayoutBinding 14 12 import com.example.util.simpletimetracker.feature_views.extension.setMargins 15 13 import com.example.util.simpletimetracker.feature_views.viewData.RecordTypeIcon ··· 112 110 113 111 var itemWithCheck: Boolean = false 114 112 set(value) { 113 + binding.viewRecordTypeItemCheckmark.itemWithCheck = value 115 114 field = value 116 - setCheckmark() 117 115 } 118 116 119 117 var itemIsChecked: Boolean = false 120 118 set(value) { 119 + binding.viewRecordTypeItemCheckmark.itemIsChecked = value 121 120 field = value 122 - setCheckmark() 123 121 } 124 122 125 123 fun getContainer(): CardView { 126 124 return binding.containerRecordTypeItem 127 125 } 128 126 129 - fun getCheckmarkOutline(): AppCompatImageView { 130 - return binding.ivRecordTypeItemCheckOutline 127 + fun getCheckmarkOutline(): GoalCheckmarkView { 128 + return binding.viewRecordTypeItemCheckmark 131 129 } 132 130 133 131 private fun changeConstraints(isRow: Boolean) = with(binding.container) { ··· 147 145 ivRecordTypeItemIcon.setMargins(start = 0) 148 146 tvRecordTypeItemName.gravity = Gravity.CENTER 149 147 tvRecordTypeItemName.setMargins(top = 4, start = 0) 150 - } 151 - } 152 - 153 - private fun setCheckmark() = with(binding) { 154 - if (itemWithCheck) { 155 - ivRecordTypeItemCheckOutline.isVisible = true 156 - ivRecordTypeItemCheck.isVisible = itemIsChecked 157 - } else { 158 - ivRecordTypeItemCheckOutline.isVisible = false 159 - ivRecordTypeItemCheck.isVisible = false 160 148 } 161 149 } 162 150 }
+4
features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/goalTime/controller/NotificationGoalTimeBroadcastController.kt
··· 1 1 package com.example.util.simpletimetracker.feature_notification.goalTime.controller 2 2 3 3 import com.example.util.simpletimetracker.domain.interactor.NotificationGoalTimeInteractor 4 + import com.example.util.simpletimetracker.domain.interactor.NotificationTypeInteractor 4 5 import com.example.util.simpletimetracker.domain.interactor.WidgetInteractor 5 6 import com.example.util.simpletimetracker.domain.model.RecordTypeGoal 6 7 import com.example.util.simpletimetracker.domain.model.WidgetType ··· 13 14 class NotificationGoalTimeBroadcastController @Inject constructor( 14 15 private val notificationGoalTimeInteractor: NotificationGoalTimeInteractor, 15 16 private val widgetInteractor: WidgetInteractor, 17 + private val notificationTypeInteractor: NotificationTypeInteractor, 16 18 ) { 17 19 18 20 fun onGoalTimeReminder(typeId: Long, goalRange: RecordTypeGoal.Range) { 19 21 GlobalScope.launch { 20 22 notificationGoalTimeInteractor.show(typeId, goalRange) 21 23 widgetInteractor.updateSingleWidgets(typeIds = listOf(typeId)) 24 + notificationTypeInteractor.checkAndShow(typeId = typeId) 22 25 } 23 26 } 24 27 ··· 26 29 GlobalScope.launch { 27 30 reschedule() 28 31 widgetInteractor.updateWidgets(listOf(WidgetType.RECORD_TYPE)) 32 + notificationTypeInteractor.updateNotifications() 29 33 } 30 34 } 31 35
+23 -3
features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/recordType/customView/NotificationIconView.kt
··· 6 6 import android.graphics.PorterDuffColorFilter 7 7 import android.util.AttributeSet 8 8 import android.view.LayoutInflater 9 - import android.widget.FrameLayout 10 - import com.example.util.simpletimetracker.feature_views.viewData.RecordTypeIcon 9 + import android.widget.RelativeLayout 11 10 import com.example.util.simpletimetracker.feature_notification.R 12 11 import com.example.util.simpletimetracker.feature_notification.databinding.NotificationIconViewLayoutBinding 12 + import com.example.util.simpletimetracker.feature_views.viewData.RecordTypeIcon 13 13 14 14 class NotificationIconView @JvmOverloads constructor( 15 15 context: Context, 16 16 attrs: AttributeSet? = null, 17 17 defStyleAttr: Int = 0 18 - ) : FrameLayout( 18 + ) : RelativeLayout( 19 19 context, 20 20 attrs, 21 21 defStyleAttr ··· 35 35 getResourceId(R.styleable.NotificationIconView_itemIcon, R.drawable.unknown) 36 36 .let(RecordTypeIcon::Image) 37 37 38 + if (hasValue(R.styleable.NotificationIconView_itemWithCheck)) { 39 + itemWithCheck = getBoolean(R.styleable.NotificationIconView_itemWithCheck, false) 40 + } 41 + 42 + if (hasValue(R.styleable.NotificationIconView_itemIsChecked)) { 43 + itemIsChecked = getBoolean(R.styleable.NotificationIconView_itemIsChecked, false) 44 + } 45 + 38 46 recycle() 39 47 } 40 48 } ··· 49 57 var itemIcon: RecordTypeIcon = RecordTypeIcon.Image(0) 50 58 set(value) { 51 59 binding.ivNotificationIcon.itemIcon = value 60 + field = value 61 + } 62 + 63 + var itemWithCheck: Boolean = false 64 + set(value) { 65 + binding.viewNotificationIconCheckmark.itemWithCheck = value 66 + field = value 67 + } 68 + 69 + var itemIsChecked: Boolean = false 70 + set(value) { 71 + binding.viewNotificationIconCheckmark.itemIsChecked = value 52 72 field = value 53 73 } 54 74 }
+40 -7
features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/recordType/interactor/NotificationTypeInteractorImpl.kt
··· 52 52 if (!prefsInteractor.getShowNotifications()) return 53 53 54 54 val recordType = recordTypeInteractor.get(typeId) 55 - val goals = recordTypeGoalInteractor.getByType(typeId) 56 - val recordTypes = recordTypeInteractor.getAll() 55 + val thisGoals = recordTypeGoalInteractor.getByType(typeId) 56 + val recordTypes = recordTypeInteractor.getAll().associateBy(RecordType::id) 57 57 val runningRecord = runningRecordInteractor.get(typeId) 58 58 val recordTags = recordTagInteractor.getAll() 59 59 val isDarkTheme = prefsInteractor.getDarkMode() 60 60 val useMilitaryTime = prefsInteractor.getUseMilitaryTimeFormat() 61 61 val showSeconds = prefsInteractor.getShowSeconds() 62 62 val showControls = prefsInteractor.getShowNotificationsControls() 63 - val goalTime = if (goals.hasDailyDuration()) { 64 - goals.getDailyDuration() 63 + val goalTime = if (thisGoals.hasDailyDuration()) { 64 + thisGoals.getDailyDuration() 65 65 } else { 66 - goals.getSessionDuration() 66 + thisGoals.getSessionDuration() 67 67 } 68 68 val viewedTags = if (selectedTypeId != 0L) { 69 69 val typedTags = recordTags.filter { it.typeId == selectedTypeId } ··· 73 73 emptyList() 74 74 } 75 75 val controls = if (showControls) { 76 + val runningRecords = runningRecordInteractor.getAll() 77 + val goals = recordTypeGoalInteractor.getAll().groupBy(RecordTypeGoal::typeId) 78 + val allDailyCurrents = if (goals.isNotEmpty()) { 79 + getCurrentRecordsDurationInteractor.getAllDailyCurrents( 80 + typesMap = recordTypes, 81 + runningRecords = runningRecords, 82 + ) 83 + } else { 84 + // No goals - no need to calculate durations. 85 + emptyMap() 86 + } 76 87 getControls( 77 88 isDarkTheme = isDarkTheme, 78 - types = recordTypes, 89 + types = recordTypes.values.toList(), 79 90 typesShift = typesShift, 80 91 tags = viewedTags, 81 92 tagsShift = tagsShift, 82 93 selectedTypeId = selectedTypeId, 94 + goals = goals, 95 + allDailyCurrents = allDailyCurrents, 83 96 ) 84 97 } else { 85 98 NotificationTypeParams.Controls.Disabled ··· 116 129 val recordTypes = recordTypeInteractor.getAll().associateBy(RecordType::id) 117 130 val goals = recordTypeGoalInteractor.getAll().groupBy(RecordTypeGoal::typeId) 118 131 val recordTags = recordTagInteractor.getAll() 132 + val runningRecords = runningRecordInteractor.getAll() 119 133 val isDarkTheme = prefsInteractor.getDarkMode() 120 134 val useMilitaryTime = prefsInteractor.getUseMilitaryTimeFormat() 121 135 val showSeconds = prefsInteractor.getShowSeconds() 122 136 val showControls = prefsInteractor.getShowNotificationsControls() 123 137 val controls = if (showControls) { 138 + val allDailyCurrents = if (goals.isNotEmpty()) { 139 + getCurrentRecordsDurationInteractor.getAllDailyCurrents( 140 + typesMap = recordTypes, 141 + runningRecords = runningRecords, 142 + ) 143 + } else { 144 + // No goals - no need to calculate durations. 145 + emptyMap() 146 + } 124 147 getControls( 125 148 isDarkTheme = isDarkTheme, 126 149 types = recordTypes.values.toList(), 150 + goals = goals, 151 + allDailyCurrents = allDailyCurrents, 127 152 ) 128 153 } else { 129 154 NotificationTypeParams.Controls.Disabled 130 155 } 131 156 132 - runningRecordInteractor.getAll() 157 + runningRecords 133 158 .forEach { runningRecord -> 134 159 val thisGoals = goals[runningRecord.id].orEmpty() 135 160 val goalTime = if (thisGoals.hasDailyDuration()) { ··· 203 228 tags: List<RecordTag> = emptyList(), 204 229 tagsShift: Int = 0, 205 230 selectedTypeId: Long? = null, 231 + goals: Map<Long, List<RecordTypeGoal>>, 232 + allDailyCurrents: Map<Long, GetCurrentRecordsDurationInteractor.Result>, 206 233 ): NotificationTypeParams.Controls = NotificationTypeParams.Controls.Enabled( 207 234 types = run { 208 235 val viewData = recordTypeViewDataMapper.mapToRepeatItem( ··· 213 240 id = REPEAT_BUTTON_ITEM_ID, 214 241 icon = viewData.iconId, 215 242 color = viewData.color, 243 + isChecked = null, 216 244 ).let(::listOf) 217 245 } + types 218 246 .filter { !it.hidden } ··· 221 249 id = type.id, 222 250 icon = type.icon.let(iconMapper::mapIcon), 223 251 color = type.color.let { colorMapper.mapToColorInt(it, isDarkTheme) }, 252 + isChecked = recordTypeViewDataMapper.mapGoalCheckmark( 253 + type = type, 254 + goals = goals, 255 + allDailyCurrents = allDailyCurrents, 256 + ), 224 257 ) 225 258 }, 226 259 typesShift = typesShift,
+9 -2
features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/recordType/manager/NotificationTypeManager.kt
··· 15 15 import androidx.core.app.NotificationCompat 16 16 import androidx.core.app.NotificationManagerCompat 17 17 import com.example.util.simpletimetracker.core.utils.PendingIntents 18 + import com.example.util.simpletimetracker.domain.extension.orFalse 18 19 import com.example.util.simpletimetracker.feature_notification.R 19 20 import com.example.util.simpletimetracker.feature_notification.recevier.NotificationReceiver 20 21 import com.example.util.simpletimetracker.feature_notification.recordType.customView.NotificationIconView ··· 175 176 getTypeControlView( 176 177 icon = it.icon, 177 178 color = it.color, 179 + isChecked = it.isChecked, 178 180 intent = getPendingSelfIntent( 179 181 context = context, 180 182 action = if (it.id == recordTypeId) { ··· 197 199 getTypeControlView( 198 200 icon = null, 199 201 color = null, 202 + isChecked = null, 200 203 intent = null 201 204 ).let { 202 205 addView(R.id.containerNotificationTypes, it) ··· 297 300 private fun getTypeControlView( 298 301 icon: RecordTypeIcon?, 299 302 color: Int?, 303 + isChecked: Boolean?, 300 304 intent: PendingIntent?, 301 305 ): RemoteViews { 302 306 return RemoteViews(context.packageName, R.layout.notification_type_layout) 303 307 .apply { 304 308 if (icon != null && color != null) { 305 309 setViewVisibility(R.id.containerNotificationType, View.VISIBLE) 306 - setImageViewBitmap(R.id.ivNotificationType, getIconBitmap(icon, color)) 310 + setImageViewBitmap(R.id.ivNotificationType, getIconBitmap(icon, color, isChecked)) 307 311 } else { 308 312 setViewVisibility(R.id.containerNotificationType, View.INVISIBLE) 309 313 } ··· 360 364 361 365 private fun getIconBitmap( 362 366 icon: RecordTypeIcon, 363 - color: Int 367 + color: Int, 368 + isChecked: Boolean? = null, 364 369 ): Bitmap { 365 370 return iconView.apply { 366 371 itemIcon = icon 367 372 itemColor = color 373 + itemWithCheck = isChecked != null 374 + itemIsChecked = isChecked.orFalse() 368 375 measureExactly(iconSize) 369 376 }.getBitmapFromView() 370 377 }
+1
features/feature_notification/src/main/java/com/example/util/simpletimetracker/feature_notification/recordType/manager/NotificationTypeParams.kt
··· 20 20 val id: Long, 21 21 val icon: RecordTypeIcon, 22 22 val color: Int, 23 + val isChecked: Boolean?, 23 24 ) 24 25 25 26 data class Tag(