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 record type changing

razeeman (May 16, 2020, 11:57 AM +0300) bdf45e87 840f8649

+248 -63
+1 -21
app/proguard-rules.pro
··· 1 - # Add project specific ProGuard rules here. 2 - # You can control the set of applied configuration files using the 3 - # proguardFiles setting in build.gradle. 4 - # 5 - # For more details, see 6 - # http://developer.android.com/guide/developing/tools/proguard.html 7 - 8 - # If your project uses WebView with JS, uncomment the following 9 - # and specify the fully qualified class name to the JavaScript interface 10 - # class: 11 - #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 - # public *; 13 - #} 14 - 15 - # Uncomment this to preserve the line number information for 16 - # debugging stack traces. 17 - #-keepattributes SourceFile,LineNumberTable 18 - 19 - # If you keep the line number information, uncomment this to 20 - # hide the original source file name. 21 - #-renamesourcefileattribute SourceFile 1 + -dontobfuscate
+1 -1
core/src/main/java/com/example/util/simpletimetracker/core/mapper/TimeMapper.kt
··· 25 25 var res = "" 26 26 if (hr > 0) res += "${hr}h " 27 27 if (hr > 0 || min > 0) res += "${min}m " 28 - res += "${sec}sec" 28 + res += "${sec}s" 29 29 30 30 return res 31 31 }
+2 -1
data_local/src/main/java/com/example/util/simpletimetracker/data_local/database/RecordDao.kt
··· 2 2 3 3 import androidx.room.Dao 4 4 import androidx.room.Insert 5 + import androidx.room.OnConflictStrategy 5 6 import androidx.room.Query 6 7 import com.example.util.simpletimetracker.data_local.model.RecordDBO 7 8 ··· 14 15 @Query("SELECT * FROM records WHERE id = :id LIMIT 1") 15 16 suspend fun get(id: Long): RecordDBO? 16 17 17 - @Insert 18 + @Insert(onConflict = OnConflictStrategy.REPLACE) 18 19 suspend fun insert(record: RecordDBO) 19 20 20 21 @Query("DELETE FROM records WHERE id = :id")
+4
domain/src/main/java/com/example/util/simpletimetracker/domain/interactor/RecordInteractor.kt
··· 28 28 } 29 29 } 30 30 31 + suspend fun add(record: Record) = withContext(Dispatchers.IO) { 32 + recordRepo.add(record) 33 + } 34 + 31 35 suspend fun remove(id: Long) = withContext(Dispatchers.IO) { 32 36 recordRepo.remove(id) 33 37 }
+14
feature_change_record/src/main/java/com/example/util/simpletimetracker/feature_change_record/adapter/ChangeRecordAdapter.kt
··· 1 + package com.example.util.simpletimetracker.feature_change_record.adapter 2 + 3 + import com.example.util.simpletimetracker.core.adapter.BaseRecyclerAdapter 4 + import com.example.util.simpletimetracker.core.adapter.ViewHolderType 5 + import com.example.util.simpletimetracker.feature_change_record.viewData.ChangeRecordTypeViewData 6 + 7 + class ChangeRecordAdapter( 8 + onItemClick: ((ChangeRecordTypeViewData) -> Unit) 9 + ) : BaseRecyclerAdapter() { 10 + 11 + init { 12 + delegates[ViewHolderType.VIEW] = ChangeRecordAdapterDelegate(onItemClick) 13 + } 14 + }
+32
feature_change_record/src/main/java/com/example/util/simpletimetracker/feature_change_record/adapter/ChangeRecordAdapterDelegate.kt
··· 1 + package com.example.util.simpletimetracker.feature_change_record.adapter 2 + 3 + import android.view.ViewGroup 4 + import com.example.util.simpletimetracker.core.adapter.BaseRecyclerAdapterDelegate 5 + import com.example.util.simpletimetracker.core.adapter.BaseRecyclerViewHolder 6 + import com.example.util.simpletimetracker.core.adapter.ViewHolderType 7 + import com.example.util.simpletimetracker.feature_change_record.R 8 + import com.example.util.simpletimetracker.feature_change_record.viewData.ChangeRecordTypeViewData 9 + import kotlinx.android.synthetic.main.item_record_layout.view.* 10 + 11 + class ChangeRecordAdapterDelegate( 12 + private val onItemClick: ((ChangeRecordTypeViewData) -> Unit) 13 + ) : BaseRecyclerAdapterDelegate() { 14 + 15 + override fun onCreateViewHolder(parent: ViewGroup): BaseRecyclerViewHolder = 16 + RunningRecordsViewHolder(parent) 17 + 18 + inner class RunningRecordsViewHolder(parent: ViewGroup) : 19 + BaseRecyclerViewHolder(parent, R.layout.item_record_layout) { 20 + 21 + override fun bind(item: ViewHolderType) = with(itemView.viewRecordTypeItem) { 22 + item as ChangeRecordTypeViewData 23 + 24 + color = item.color 25 + icon = item.icon 26 + name = item.name 27 + setOnClickListener { 28 + onItemClick.invoke(item) 29 + } 30 + } 31 + } 32 + }
+29
feature_change_record/src/main/java/com/example/util/simpletimetracker/feature_change_record/mapper/ChangeRecordTypeViewDataMapper.kt
··· 1 + package com.example.util.simpletimetracker.feature_change_record.mapper 2 + 3 + import com.example.util.simpletimetracker.core.mapper.ColorMapper 4 + import com.example.util.simpletimetracker.core.mapper.IconMapper 5 + import com.example.util.simpletimetracker.core.mapper.TimeMapper 6 + import com.example.util.simpletimetracker.core.repo.ResourceRepo 7 + import com.example.util.simpletimetracker.domain.model.RecordType 8 + import com.example.util.simpletimetracker.feature_change_record.viewData.ChangeRecordTypeViewData 9 + import javax.inject.Inject 10 + 11 + class ChangeRecordTypeViewDataMapper @Inject constructor( 12 + private val iconMapper: IconMapper, 13 + private val colorMapper: ColorMapper, 14 + private val timeMapper: TimeMapper, 15 + private val resourceRepo: ResourceRepo 16 + ) { 17 + 18 + fun map(recordType: RecordType): ChangeRecordTypeViewData { 19 + return ChangeRecordTypeViewData( 20 + id = recordType.id, 21 + name = recordType.name, 22 + icon = recordType.icon 23 + .let(iconMapper::mapToDrawableResId), 24 + color = recordType.color 25 + .let(colorMapper::mapToColorResId) 26 + .let(resourceRepo::getColor) 27 + ) 28 + } 29 + }
+18 -25
feature_change_record/src/main/java/com/example/util/simpletimetracker/feature_change_record/mapper/ChangeRecordViewDataMapper.kt
··· 17 17 private val resourceRepo: ResourceRepo 18 18 ) { 19 19 20 - fun map(record: Record, recordType: RecordType): ChangeRecordViewData { 20 + fun map(record: Record?, recordType: RecordType?): ChangeRecordViewData { 21 21 return ChangeRecordViewData( 22 - name = recordType.name, 23 - timeStarted = record.timeStarted 24 - .let(timeMapper::formatTime), 25 - timeFinished = record.timeEnded 26 - .let(timeMapper::formatTime), 27 - duration = (record.timeEnded - record.timeStarted) 28 - .let(timeMapper::formatInterval), 29 - iconId = recordType.icon 30 - .let(iconMapper::mapToDrawableResId), 31 - color = recordType.color 32 - .let(colorMapper::mapToColorResId) 33 - .let(resourceRepo::getColor) 34 - ) 35 - } 36 - 37 - fun mapToEmpty(): ChangeRecordViewData { 38 - return ChangeRecordViewData( 39 - name = "", 40 - timeStarted = "", 41 - timeFinished = "", 42 - duration = "", 43 - iconId = R.drawable.ic_unknown, 44 - color = ColorMapper.availableColors 45 - .random() 22 + name = recordType?.name.orEmpty(), 23 + timeStarted = record?.timeStarted 24 + ?.let(timeMapper::formatTime) 25 + .orEmpty(), 26 + timeFinished = record?.timeEnded 27 + ?.let(timeMapper::formatTime) 28 + .orEmpty(), 29 + duration = record 30 + ?.let { it.timeEnded - it.timeStarted } 31 + ?.let(timeMapper::formatInterval) 32 + .orEmpty(), 33 + iconId = recordType?.icon 34 + ?.let(iconMapper::mapToDrawableResId) 35 + ?: R.drawable.ic_unknown, 36 + color = (recordType?.color 37 + ?.let(colorMapper::mapToColorResId) 38 + ?: ColorMapper.availableColors.random()) 46 39 .let(resourceRepo::getColor) 47 40 ) 48 41 }
+41 -2
feature_change_record/src/main/java/com/example/util/simpletimetracker/feature_change_record/view/ChangeRecordFragment.kt
··· 8 8 import androidx.fragment.app.viewModels 9 9 import androidx.lifecycle.observe 10 10 import com.example.util.simpletimetracker.core.extension.observeOnce 11 + import com.example.util.simpletimetracker.core.extension.rotateDown 12 + import com.example.util.simpletimetracker.core.extension.rotateUp 11 13 import com.example.util.simpletimetracker.core.extension.visible 12 14 import com.example.util.simpletimetracker.domain.extension.orZero 13 15 import com.example.util.simpletimetracker.feature_change_record.R 16 + import com.example.util.simpletimetracker.feature_change_record.adapter.ChangeRecordAdapter 14 17 import com.example.util.simpletimetracker.feature_change_record.di.ChangeRecordComponentProvider 15 18 import com.example.util.simpletimetracker.feature_change_record.viewData.ChangeRecordViewData 16 19 import com.example.util.simpletimetracker.feature_change_record.viewModel.ChangeRecordViewModel 17 20 import com.example.util.simpletimetracker.feature_change_record.viewModel.ChangeRecordViewModelFactory 18 21 import com.example.util.simpletimetracker.navigation.params.ChangeRecordParams 22 + import com.google.android.flexbox.FlexDirection 23 + import com.google.android.flexbox.FlexWrap 24 + import com.google.android.flexbox.FlexboxLayoutManager 25 + import com.google.android.flexbox.JustifyContent 19 26 import kotlinx.android.synthetic.main.change_record_fragment.* 20 27 21 28 class ChangeRecordFragment : Fragment() { ··· 27 34 ) 28 35 } 29 36 ) 37 + private val typesAdapter: ChangeRecordAdapter by lazy { 38 + ChangeRecordAdapter(viewModel::onTypeClick) 39 + } 30 40 31 41 override fun onCreateView( 32 42 inflater: LayoutInflater, ··· 46 56 (activity?.application as ChangeRecordComponentProvider) 47 57 .changeRecordComponent?.inject(viewModel) 48 58 59 + rvChangeRecordType.apply { 60 + layoutManager = FlexboxLayoutManager(requireContext()).apply { 61 + flexDirection = FlexDirection.ROW 62 + justifyContent = JustifyContent.CENTER 63 + flexWrap = FlexWrap.WRAP 64 + } 65 + adapter = typesAdapter 66 + } 67 + 49 68 viewModel.deleteIconVisibility.observeOnce(viewLifecycleOwner) { 50 69 btnChangeRecordDelete.visible = it 51 70 } ··· 54 73 updatePreview(it) 55 74 } 56 75 76 + viewModel.types.observe(viewLifecycleOwner) { 77 + typesAdapter.replace(it) 78 + } 79 + 80 + viewModel.flipTypesChooser.observe(viewLifecycleOwner) { opened -> 81 + rvChangeRecordType.visible = opened 82 + arrowChangeRecordType.apply { 83 + if (opened) rotateDown() else rotateUp() 84 + } 85 + } 86 + 87 + viewModel.saveButtonEnabled.observe(viewLifecycleOwner) { enabled -> 88 + btnChangeRecordSave.isEnabled = enabled 89 + } 90 + 91 + fieldChangeRecordType.setOnClickListener { 92 + viewModel.onTypeChooserClick() 93 + } 94 + 57 95 btnChangeRecordDelete.setOnClickListener { 58 96 it.isEnabled = false 59 97 viewModel.onDeleteClick() 60 98 } 61 99 62 100 btnChangeRecordSave.setOnClickListener { 63 - it.isEnabled = false 64 101 viewModel.onSaveClick() 65 102 } 66 103 } 67 104 68 105 private fun updatePreview(item: ChangeRecordViewData) { 69 - // TODO change recordType preview to record preview 70 106 with(previewChangeRecord) { 71 107 name = item.name 72 108 icon = item.iconId 73 109 color = item.color 110 + timeStarted = item.timeStarted 111 + timeEnded = item.timeFinished 112 + duration = item.duration 74 113 } 75 114 } 76 115
+15
feature_change_record/src/main/java/com/example/util/simpletimetracker/feature_change_record/viewData/ChangeRecordTypeViewData.kt
··· 1 + package com.example.util.simpletimetracker.feature_change_record.viewData 2 + 3 + import androidx.annotation.ColorInt 4 + import androidx.annotation.DrawableRes 5 + import com.example.util.simpletimetracker.core.adapter.ViewHolderType 6 + 7 + data class ChangeRecordTypeViewData( 8 + val id: Long, 9 + val name: String, 10 + @DrawableRes val icon: Int, 11 + @ColorInt val color: Int 12 + ) : ViewHolderType { 13 + 14 + override fun getViewType(): Int = ViewHolderType.VIEW 15 + }
+80 -8
feature_change_record/src/main/java/com/example/util/simpletimetracker/feature_change_record/viewModel/ChangeRecordViewModel.kt
··· 4 4 import androidx.lifecycle.MutableLiveData 5 5 import androidx.lifecycle.ViewModel 6 6 import androidx.lifecycle.viewModelScope 7 + import com.example.util.simpletimetracker.core.adapter.ViewHolderType 7 8 import com.example.util.simpletimetracker.core.repo.ResourceRepo 9 + import com.example.util.simpletimetracker.domain.extension.flip 10 + import com.example.util.simpletimetracker.domain.extension.orTrue 11 + import com.example.util.simpletimetracker.domain.extension.orZero 8 12 import com.example.util.simpletimetracker.domain.interactor.RecordInteractor 9 13 import com.example.util.simpletimetracker.domain.interactor.RecordTypeInteractor 14 + import com.example.util.simpletimetracker.domain.model.Record 10 15 import com.example.util.simpletimetracker.feature_change_record.R 16 + import com.example.util.simpletimetracker.feature_change_record.mapper.ChangeRecordTypeViewDataMapper 11 17 import com.example.util.simpletimetracker.feature_change_record.mapper.ChangeRecordViewDataMapper 18 + import com.example.util.simpletimetracker.feature_change_record.viewData.ChangeRecordTypeViewData 12 19 import com.example.util.simpletimetracker.feature_change_record.viewData.ChangeRecordViewData 13 20 import com.example.util.simpletimetracker.navigation.Router 14 21 import kotlinx.coroutines.launch ··· 27 34 @Inject 28 35 lateinit var changeRecordViewDataMapper: ChangeRecordViewDataMapper 29 36 @Inject 37 + lateinit var changeRecordTypeViewDataMapper: ChangeRecordTypeViewDataMapper 38 + @Inject 30 39 lateinit var resourceRepo: ResourceRepo 31 40 32 41 val record: LiveData<ChangeRecordViewData> by lazy { 33 42 return@lazy MutableLiveData<ChangeRecordViewData>().let { initial -> 34 - viewModelScope.launch { initial.value = loadRecordViewData() } 43 + viewModelScope.launch { 44 + initializePreviewViewData() 45 + initial.value = loadPreviewViewData() 46 + } 35 47 initial 36 48 } 37 49 } 50 + val types: LiveData<List<ViewHolderType>> by lazy { 51 + return@lazy MutableLiveData<List<ViewHolderType>>().let { initial -> 52 + viewModelScope.launch { initial.value = loadTypesViewData() } 53 + initial 54 + } 55 + } 56 + val flipTypesChooser: LiveData<Boolean> = MutableLiveData() 38 57 val deleteIconVisibility: LiveData<Boolean> = MutableLiveData(id != 0L) 58 + val saveButtonEnabled: LiveData<Boolean> = MutableLiveData(true) 59 + 60 + private var newTypeId: Long = 0 61 + private var newTimeStarted: Long = System.currentTimeMillis() - 1000 * 60 * 60 62 + private var newTimeEnded: Long = System.currentTimeMillis() 63 + 64 + fun onTypeChooserClick() { 65 + (flipTypesChooser as MutableLiveData).value = flipTypesChooser.value 66 + ?.flip().orTrue() 67 + } 39 68 40 69 fun onDeleteClick() { 41 70 viewModelScope.launch { ··· 49 78 } 50 79 51 80 fun onSaveClick() { 52 - // TODO 53 - router.back() 81 + if (newTypeId == 0L) { 82 + resourceRepo.getString(R.string.message_choose_type) 83 + .let(router::showSystemMessage) 84 + return 85 + } 86 + (saveButtonEnabled as MutableLiveData).value = false 87 + viewModelScope.launch { 88 + Record( 89 + id = id, 90 + typeId = newTypeId, 91 + timeStarted = newTimeStarted, 92 + timeEnded = newTimeEnded 93 + ).let { 94 + recordInteractor.add(it) 95 + router.back() 96 + } 97 + } 54 98 } 55 99 56 - private suspend fun loadRecordViewData(): ChangeRecordViewData { 100 + fun onTypeClick(item: ChangeRecordTypeViewData) { 101 + viewModelScope.launch { 102 + if (item.id != newTypeId) { 103 + newTypeId = item.id 104 + updatePreview() 105 + } 106 + } 107 + } 108 + 109 + private suspend fun updatePreview() { 110 + (record as MutableLiveData).value = loadPreviewViewData() 111 + } 112 + 113 + private suspend fun initializePreviewViewData() { 57 114 if (id != 0L) { 58 115 recordInteractor.get(id)?.let { record -> 59 - recordTypeInteractor.get(record.typeId)?.let { recordType -> 60 - return changeRecordViewDataMapper.map(record, recordType) 61 - } 116 + newTypeId = record.typeId.orZero() 117 + newTimeStarted = record.timeStarted 118 + newTimeEnded = record.timeEnded 62 119 } 63 120 } 64 - return changeRecordViewDataMapper.mapToEmpty() 121 + } 122 + 123 + private suspend fun loadPreviewViewData(): ChangeRecordViewData { 124 + val record = Record( 125 + typeId = newTypeId, 126 + timeStarted = newTimeStarted, 127 + timeEnded = newTimeEnded 128 + ) 129 + val type = recordTypeInteractor.get(newTypeId) 130 + return changeRecordViewDataMapper.map(record, type) 131 + } 132 + 133 + private suspend fun loadTypesViewData(): List<ViewHolderType> { 134 + return recordTypeInteractor.getAll() 135 + .filter { !it.hidden } 136 + .map(changeRecordTypeViewDataMapper::map) 65 137 } 66 138 }
+7 -4
feature_change_record/src/main/res/layout/change_record_fragment.xml
··· 21 21 app:layout_constraintTop_toTopOf="parent" 22 22 tools:visibility="visible" /> 23 23 24 - <com.example.util.simpletimetracker.core.view.RecordTypeView 24 + <com.example.util.simpletimetracker.core.view.RecordView 25 25 android:id="@+id/previewChangeRecord" 26 - android:layout_width="wrap_content" 26 + android:layout_width="0dp" 27 27 android:layout_height="wrap_content" 28 28 android:layout_marginTop="8dp" 29 - app:layout_constraintEnd_toEndOf="parent" 29 + app:layout_constraintEnd_toStartOf="@id/btnChangeRecordDelete" 30 30 app:layout_constraintStart_toStartOf="parent" 31 31 app:layout_constraintTop_toTopOf="parent" 32 - tools:name="Name" /> 32 + tools:duration="5h 23m 3s" 33 + tools:name="Name" 34 + tools:timeEnded="15.02.2020 11:58" 35 + tools:timeStarted="15.02.2020 07:35" /> 33 36 34 37 <androidx.cardview.widget.CardView 35 38 android:id="@+id/fieldChangeRecordType"
+3 -1
feature_change_record/src/main/res/layout/item_record_layout.xml
··· 1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 <com.example.util.simpletimetracker.core.view.RecordTypeView xmlns:android="http://schemas.android.com/apk/res/android" 3 + xmlns:tools="http://schemas.android.com/tools" 3 4 android:id="@+id/viewRecordTypeItem" 4 5 android:layout_width="wrap_content" 5 - android:layout_height="wrap_content" /> 6 + android:layout_height="wrap_content" 7 + tools:name="Item name" />
+1
feature_change_record/src/main/res/values/strings.xml
··· 1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 <resources> 3 3 <string name="record_removed">Record removed</string> 4 + <string name="message_choose_type">Select activity</string> 4 5 </resources>