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.

change wear tag selection

razeeman (Mar 3, 2024, 1:44 PM +0300) 524c0479 a6e00bbc

+355 -139
+3 -2
wear/src/main/java/com/example/util/simpletimetracker/presentation/components/ActivitiesList.kt
··· 15 15 import androidx.wear.tooling.preview.devices.WearDevices 16 16 import com.example.util.simpletimetracker.R 17 17 import com.example.util.simpletimetracker.presentation.layout.ScaffoldedScrollingColumn 18 + import com.example.util.simpletimetracker.presentation.utils.getString 18 19 import com.example.util.simpletimetracker.wear_api.WearActivity 19 20 import com.example.util.simpletimetracker.wear_api.WearCurrentActivity 20 21 ··· 31 32 if (activities.isEmpty()) { 32 33 item { 33 34 Text( 34 - LocalContext.current.getString(R.string.no_activities), 35 + getString(R.string.no_activities), 35 36 modifier = Modifier.padding(8.dp), 36 37 ) 37 38 } ··· 40 41 val currentActivity = currentActivities.filter { it.id == activity.id }.getOrNull(0) 41 42 item(key = activity.id) { 42 43 ActivityChip( 43 - activity, 44 + activity = activity, 44 45 startedAt = currentActivity?.startedAt, 45 46 tags = currentActivity?.tags.orEmpty(), 46 47 onClick = { onSelectActivity(activity) },
-41
wear/src/main/java/com/example/util/simpletimetracker/presentation/components/SubmitButton.kt
··· 1 - /* 2 - * This Source Code Form is subject to the terms of the Mozilla Public 3 - * License, v. 2.0. If a copy of the MPL was not distributed with this 4 - * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 - */ 6 - package com.example.util.simpletimetracker.presentation.components 7 - 8 - import androidx.compose.foundation.layout.padding 9 - import androidx.compose.material.icons.Icons 10 - import androidx.compose.material.icons.automirrored.rounded.Send 11 - import androidx.compose.runtime.Composable 12 - import androidx.compose.ui.Modifier 13 - import androidx.compose.ui.platform.LocalContext 14 - import androidx.compose.ui.tooling.preview.Preview 15 - import androidx.compose.ui.unit.dp 16 - import androidx.wear.compose.material.Icon 17 - import androidx.wear.compose.material.OutlinedButton 18 - import com.example.util.simpletimetracker.R 19 - 20 - @Composable 21 - fun SubmitButton(onClick: () -> Unit, contentDescription: String? = null) { 22 - OutlinedButton( 23 - onClick = onClick, 24 - content = { 25 - Icon( 26 - Icons.AutoMirrored.Rounded.Send, 27 - contentDescription = contentDescription 28 - ?: LocalContext.current.getString(R.string.submit_button_default_content_description), 29 - ) 30 - }, 31 - modifier = Modifier.padding(all = 8.dp), 32 - ) 33 - } 34 - 35 - @Preview 36 - @Composable 37 - private fun Preview() { 38 - SubmitButton( 39 - onClick = { /* Log.i("Preview", "Refresh Button clicked!") */ }, 40 - ) 41 - }
+15 -11
wear/src/main/java/com/example/util/simpletimetracker/presentation/components/TagChip.kt
··· 21 21 import androidx.wear.compose.material.Text 22 22 import androidx.wear.compose.material.ToggleChipDefaults 23 23 import androidx.wear.tooling.preview.devices.WearDevices 24 + import com.example.util.simpletimetracker.presentation.theme.wearColors 24 25 import com.example.util.simpletimetracker.wear_api.WearTag 25 26 26 27 enum class TagSelectionMode { ··· 31 32 @Composable 32 33 fun TagChip( 33 34 tag: WearTag, 34 - onClick: () -> Unit = {}, 35 - onToggleClick: (WearTag) -> Unit = {}, 35 + onClick: (WearTag) -> Unit = {}, 36 36 mode: TagSelectionMode = TagSelectionMode.SINGLE, 37 37 checked: Boolean, 38 38 ) { 39 39 when (mode) { 40 40 TagSelectionMode.SINGLE -> { 41 - SingleSelectTagChip(tag = tag, onClick = onClick) 41 + SingleSelectTagChip( 42 + tag = tag, 43 + onClick = onClick, 44 + ) 42 45 } 43 46 44 47 TagSelectionMode.MULTI -> { 45 48 MultiSelectTagChip( 46 49 tag = tag, 47 50 onClick = onClick, 48 - onToggleClick = onToggleClick, 49 51 checked = checked, 50 52 ) 51 53 } 52 54 } 53 - 54 55 } 55 56 56 57 @Composable 57 58 private fun SingleSelectTagChip( 58 59 tag: WearTag, 59 - onClick: () -> Unit, 60 + onClick: (WearTag) -> Unit, 60 61 ) { 61 62 Chip( 62 - onClick = onClick, 63 + onClick = { 64 + onClick(tag) 65 + }, 63 66 label = { 64 67 Text( 65 68 text = tag.name, ··· 77 80 @Composable 78 81 private fun MultiSelectTagChip( 79 82 tag: WearTag, 80 - onClick: () -> Unit = {}, 81 - onToggleClick: (WearTag) -> Unit = {}, 83 + onClick: (WearTag) -> Unit = {}, 82 84 checked: Boolean, 83 85 ) { 84 86 SplitToggleChip( 85 87 checked = checked, 86 88 onCheckedChange = { 87 - onToggleClick(tag) 89 + onClick(tag) 90 + }, 91 + onClick = { 92 + onClick(tag) 88 93 }, 89 - onClick = onClick, 90 94 label = { 91 95 Text( 92 96 text = tag.name,
+106 -45
wear/src/main/java/com/example/util/simpletimetracker/presentation/components/TagList.kt
··· 5 5 */ 6 6 package com.example.util.simpletimetracker.presentation.components 7 7 8 + import androidx.annotation.StringRes 8 9 import androidx.compose.foundation.layout.padding 9 10 import androidx.compose.runtime.Composable 10 - import androidx.compose.runtime.getValue 11 - import androidx.compose.runtime.mutableStateOf 12 - import androidx.compose.runtime.remember 13 - import androidx.compose.runtime.setValue 14 11 import androidx.compose.ui.Modifier 15 - import androidx.compose.ui.platform.LocalContext 12 + import androidx.compose.ui.graphics.Color 16 13 import androidx.compose.ui.tooling.preview.Preview 17 14 import androidx.compose.ui.unit.dp 15 + import androidx.wear.compose.material.ScalingLazyListScope 18 16 import androidx.wear.compose.material.Text 19 17 import androidx.wear.tooling.preview.devices.WearDevices 20 18 import com.example.util.simpletimetracker.R 21 19 import com.example.util.simpletimetracker.presentation.layout.ScaffoldedScrollingColumn 20 + import com.example.util.simpletimetracker.presentation.utils.getString 22 21 import com.example.util.simpletimetracker.wear_api.WearTag 23 22 23 + sealed interface TagListState { 24 + 25 + data class Empty( 26 + @StringRes val messageResId: Int, 27 + ) : TagListState 28 + 29 + data class Content( 30 + val items: List<Item>, 31 + val mode: TagSelectionMode, 32 + ) : TagListState 33 + 34 + sealed interface Item { 35 + data class Tag( 36 + val tag: WearTag, 37 + val selected: Boolean, 38 + ) : Item 39 + 40 + data class Button( 41 + @StringRes val textResId: Int, 42 + val color: Color, 43 + val buttonType: ButtonType, 44 + ) : Item 45 + 46 + sealed interface ButtonType { 47 + object Complete : ButtonType 48 + object Untagged : ButtonType 49 + } 50 + } 51 + } 52 + 24 53 @Composable 25 54 fun TagList( 26 - tags: List<WearTag>, 27 - selectedTags: List<WearTag>, 28 - mode: TagSelectionMode, 29 - onSelectionComplete: () -> Unit = {}, 55 + state: TagListState, 56 + onButtonClick: (TagListState.Item.ButtonType) -> Unit = {}, 30 57 onToggleClick: (WearTag) -> Unit = {}, 31 58 ) { 32 59 ScaffoldedScrollingColumn { 33 - if (tags.isEmpty()) { 34 - item { 35 - Text( 36 - text = LocalContext.current.getString(R.string.no_tags), 37 - modifier = Modifier.padding(8.dp), 60 + when (state) { 61 + is TagListState.Empty -> renderEmptyState( 62 + state = state, 63 + ) 64 + is TagListState.Content -> renderContentState( 65 + state = state, 66 + onButtonClick = onButtonClick, 67 + onToggleClick = onToggleClick, 68 + ) 69 + } 70 + } 71 + } 72 + 73 + private fun ScalingLazyListScope.renderEmptyState( 74 + state: TagListState.Empty, 75 + ) { 76 + item { 77 + Text( 78 + text = getString(state.messageResId), 79 + modifier = Modifier.padding(8.dp), 80 + ) 81 + } 82 + } 83 + 84 + private fun ScalingLazyListScope.renderContentState( 85 + state: TagListState.Content, 86 + onButtonClick: (TagListState.Item.ButtonType) -> Unit = {}, 87 + onToggleClick: (WearTag) -> Unit = {}, 88 + ) { 89 + for (item in state.items) { 90 + when (item) { 91 + is TagListState.Item.Tag -> item { 92 + TagChip( 93 + tag = item.tag, 94 + mode = state.mode, 95 + onClick = onToggleClick, 96 + checked = item.selected, 38 97 ) 39 98 } 40 - } else { 41 - for (tag in tags) { 42 - item { 43 - TagChip( 44 - tag = tag, 45 - mode = mode, 46 - onClick = onSelectionComplete, 47 - onToggleClick = onToggleClick, 48 - checked = tag.id in selectedTags.map { it.id }, 49 - ) 50 - } 99 + is TagListState.Item.Button -> item { 100 + TagSelectionButton( 101 + text = getString(item.textResId), 102 + color = item.color, 103 + onClick = { 104 + onButtonClick(item.buttonType) 105 + }, 106 + ) 51 107 } 52 108 } 53 - item { 54 - SubmitButton( 55 - onClick = onSelectionComplete, 56 - ) 57 - } 58 109 } 59 110 } 60 111 ··· 62 113 @Composable 63 114 private fun NoTags() { 64 115 TagList( 65 - tags = emptyList(), 66 - selectedTags = emptyList(), 67 - mode = TagSelectionMode.SINGLE, 116 + state = TagListState.Empty(R.string.no_tags), 68 117 ) 69 118 } 70 119 ··· 72 121 @Composable 73 122 private fun WithSomeTags() { 74 123 TagList( 75 - tags = listOf( 76 - WearTag(id = 123, name = "Sleep", isGeneral = false, color = 0xFF123456), 77 - WearTag(id = 124, name = "Personal", isGeneral = true, color = 0xFF123456), 124 + state = TagListState.Content( 125 + items = listOf( 126 + TagListState.Item.Tag( 127 + tag = WearTag(id = 123, name = "Sleep", isGeneral = false, color = 0xFF123456), 128 + selected = false, 129 + ), 130 + TagListState.Item.Tag( 131 + tag = WearTag(id = 124, name = "Personal", isGeneral = true, color = 0xFF123456), 132 + selected = false, 133 + ), 134 + ), 135 + mode = TagSelectionMode.SINGLE, 78 136 ), 79 - selectedTags = emptyList(), 80 - mode = TagSelectionMode.SINGLE, 81 137 ) 82 138 } 83 139 ··· 85 141 @Composable 86 142 private fun MultiSelectMode() { 87 143 TagList( 88 - tags = listOf( 89 - WearTag(id = 123, name = "Sleep", isGeneral = false, color = 0xFF123456), 90 - WearTag(id = 124, name = "Personal", isGeneral = true, color = 0xFF123456), 91 - ), 92 - selectedTags = listOf( 93 - WearTag(id = 123, name = "Sleep", isGeneral = false, color = 0xFF123456), 144 + state = TagListState.Content( 145 + items = listOf( 146 + TagListState.Item.Tag( 147 + tag = WearTag(id = 123, name = "Sleep", isGeneral = false, color = 0xFF123456), 148 + selected = true, 149 + ), 150 + TagListState.Item.Tag( 151 + tag = WearTag(id = 124, name = "Personal", isGeneral = true, color = 0xFF123456), 152 + selected = false, 153 + ), 154 + ), 155 + mode = TagSelectionMode.MULTI, 94 156 ), 95 - mode = TagSelectionMode.MULTI, 96 157 ) 97 158 }
+66
wear/src/main/java/com/example/util/simpletimetracker/presentation/components/TagSelectionButton.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.presentation.components 7 + 8 + import androidx.compose.foundation.layout.fillMaxWidth 9 + import androidx.compose.foundation.layout.padding 10 + import androidx.compose.runtime.Composable 11 + import androidx.compose.ui.Modifier 12 + import androidx.compose.ui.graphics.Color 13 + import androidx.compose.ui.text.style.TextAlign 14 + import androidx.compose.ui.text.style.TextOverflow 15 + import androidx.compose.ui.tooling.preview.Preview 16 + import androidx.compose.ui.unit.dp 17 + import androidx.wear.compose.material.Chip 18 + import androidx.wear.compose.material.ChipDefaults 19 + import androidx.wear.compose.material.Text 20 + import androidx.wear.tooling.preview.devices.WearDevices 21 + import com.example.util.simpletimetracker.presentation.theme.ColorActive 22 + import com.example.util.simpletimetracker.presentation.theme.ColorInactive 23 + 24 + @Composable 25 + fun TagSelectionButton( 26 + text: String, 27 + color: Color, 28 + onClick: () -> Unit = {}, 29 + ) { 30 + Chip( 31 + onClick = onClick, 32 + label = { 33 + Text( 34 + modifier = Modifier.fillMaxWidth(), 35 + text = text, 36 + maxLines = 1, 37 + textAlign = TextAlign.Center, 38 + overflow = TextOverflow.Ellipsis, 39 + ) 40 + }, 41 + colors = ChipDefaults.chipColors( 42 + backgroundColor = color, 43 + ), 44 + modifier = Modifier 45 + .fillMaxWidth() 46 + .padding(top = 10.dp), 47 + ) 48 + } 49 + 50 + @Preview(device = WearDevices.LARGE_ROUND) 51 + @Composable 52 + private fun Untagged() { 53 + TagSelectionButton( 54 + text = "Untagged", 55 + color = ColorInactive, 56 + ) 57 + } 58 + 59 + @Preview(device = WearDevices.LARGE_ROUND) 60 + @Composable 61 + private fun Save() { 62 + TagSelectionButton( 63 + text = "Save", 64 + color = ColorActive, 65 + ) 66 + }
+2 -4
wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/TagsScreen.kt
··· 29 29 } 30 30 31 31 TagList( 32 - tags = state.tags, 33 - selectedTags = state.selectedTags, 34 - mode = state.mode, 35 - onSelectionComplete = viewModel::onSelectionComplete, 32 + state = state.listState, 33 + onButtonClick = viewModel::onButtonClick, 36 34 onToggleClick = viewModel::onToggleClick 37 35 ) 38 36 }
+92
wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/TagsViewDataMapper.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.presentation.screens 7 + 8 + import com.example.util.simpletimetracker.R 9 + import com.example.util.simpletimetracker.presentation.components.TagListState 10 + import com.example.util.simpletimetracker.presentation.components.TagSelectionMode 11 + import com.example.util.simpletimetracker.presentation.theme.ColorActive 12 + import com.example.util.simpletimetracker.presentation.theme.ColorInactive 13 + import com.example.util.simpletimetracker.wear_api.WearSettings 14 + import com.example.util.simpletimetracker.wear_api.WearTag 15 + import javax.inject.Inject 16 + 17 + class TagsViewDataMapper @Inject constructor() { 18 + 19 + fun mapState( 20 + tags: List<WearTag>, 21 + selectedTags: List<WearTag>, 22 + settings: WearSettings, 23 + ): TagsViewModel.State { 24 + val listState = if (tags.isEmpty()) { 25 + mapEmptyState() 26 + } else { 27 + mapContentState( 28 + tags = tags, 29 + selectedTags = selectedTags, 30 + settings = settings, 31 + ) 32 + } 33 + 34 + return TagsViewModel.State( 35 + listState = listState, 36 + settings = settings, 37 + ) 38 + } 39 + 40 + private fun mapEmptyState(): TagListState.Empty { 41 + return TagListState.Empty(R.string.no_tags) 42 + } 43 + 44 + private fun mapContentState( 45 + tags: List<WearTag>, 46 + selectedTags: List<WearTag>, 47 + settings: WearSettings, 48 + ): TagListState.Content { 49 + val selectedTagIds = selectedTags.map { it.id } 50 + 51 + val mode = if (settings.recordTagSelectionCloseAfterOne) { 52 + TagSelectionMode.SINGLE 53 + } else { 54 + TagSelectionMode.MULTI 55 + } 56 + 57 + val items = tags.map { 58 + TagListState.Item.Tag( 59 + tag = it, 60 + selected = it.id in selectedTagIds, 61 + ) 62 + } 63 + 64 + val buttons = if (mode == TagSelectionMode.SINGLE) { 65 + listOf( 66 + TagListState.Item.Button( 67 + textResId = R.string.untagged, 68 + color = ColorInactive, 69 + buttonType = TagListState.Item.ButtonType.Untagged, 70 + ), 71 + ) 72 + } else { 73 + listOf( 74 + TagListState.Item.Button( 75 + textResId = R.string.untagged, 76 + color = ColorInactive, 77 + buttonType = TagListState.Item.ButtonType.Untagged, 78 + ), 79 + TagListState.Item.Button( 80 + textResId = R.string.save, 81 + color = ColorActive, 82 + buttonType = TagListState.Item.ButtonType.Complete, 83 + ), 84 + ) 85 + } 86 + 87 + return TagListState.Content( 88 + items = items + buttons, 89 + mode = mode, 90 + ) 91 + } 92 + }
+58 -34
wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/TagsViewModel.kt
··· 7 7 8 8 import androidx.lifecycle.ViewModel 9 9 import androidx.lifecycle.viewModelScope 10 - import com.example.util.simpletimetracker.presentation.components.TagSelectionMode 10 + import com.example.util.simpletimetracker.R 11 + import com.example.util.simpletimetracker.presentation.components.TagListState 11 12 import com.example.util.simpletimetracker.presentation.data.WearRPCClient 12 13 import com.example.util.simpletimetracker.presentation.mediators.CurrentActivitiesMediator 13 14 import com.example.util.simpletimetracker.wear_api.WearSettings ··· 23 24 class TagsViewModel @Inject constructor( 24 25 private val rpc: WearRPCClient, 25 26 private val currentActivitiesMediator: CurrentActivitiesMediator, 27 + private val tagsViewDataMapper: TagsViewDataMapper, 26 28 ) : ViewModel() { 27 29 28 30 val state = MutableStateFlow(State.Empty) ··· 33 35 34 36 private var isInitialized = false 35 37 private var activityId: Long? = null 38 + private var tags: List<WearTag> = emptyList() 39 + private var selectedTags: List<WearTag> = emptyList() 40 + private var settings: WearSettings = WearSettings( 41 + allowMultitasking = false, 42 + showRecordTagSelection = false, 43 + recordTagSelectionCloseAfterOne = false, 44 + recordTagSelectionEvenForGeneralTags = false, 45 + ) 36 46 37 47 // TODO switch to savedStateHandle 38 48 fun init(activityId: Long) { 39 49 if (isInitialized) return 40 50 this.activityId = activityId 41 - refresh() 51 + loadData() 42 52 isInitialized = true 43 53 } 44 54 45 - fun onSelectionComplete() { 46 - viewModelScope.launch { 47 - val activityId = this@TagsViewModel.activityId ?: return@launch 48 - currentActivitiesMediator.start( 49 - activityId = activityId, 50 - tags = state.value.selectedTags, 51 - ) 52 - effects.emit(Effect.OnComplete) 55 + fun onButtonClick(buttonType: TagListState.Item.ButtonType) = viewModelScope.launch { 56 + when (buttonType) { 57 + is TagListState.Item.ButtonType.Untagged -> { 58 + selectedTags = emptyList() 59 + if (settings.recordTagSelectionCloseAfterOne) { 60 + startActivity() 61 + } else { 62 + state.value = mapState() 63 + } 64 + } 65 + is TagListState.Item.ButtonType.Complete -> { 66 + startActivity() 67 + } 53 68 } 54 69 } 55 70 56 - fun onToggleClick(tag: WearTag) { 57 - val currentSelectedTags = state.value.selectedTags.toMutableList() 58 - val newSelectedTags = currentSelectedTags.apply { 71 + fun onToggleClick(tag: WearTag) = viewModelScope.launch { 72 + val currentSelectedTags = selectedTags.toMutableList() 73 + selectedTags = currentSelectedTags.apply { 59 74 if (tag in this) remove(tag) else add(tag) 60 75 } 61 - val newState = state.value.copy(selectedTags = newSelectedTags) 62 - state.value = newState 76 + 77 + if (settings.recordTagSelectionCloseAfterOne) { 78 + startActivity() 79 + } else { 80 + state.value = mapState() 81 + } 63 82 } 64 83 65 - private fun refresh() { 84 + private fun loadData() { 66 85 viewModelScope.launch { 67 86 val activityId = this@TagsViewModel.activityId ?: return@launch 68 - val settings = rpc.querySettings() 69 - val newState = State( 70 - tags = rpc.queryTagsForActivity(activityId), 71 - selectedTags = emptyList(), 72 - mode = if (settings.recordTagSelectionCloseAfterOne) { 73 - TagSelectionMode.SINGLE 74 - } else { 75 - TagSelectionMode.MULTI 76 - }, 77 - settings = settings, 78 - ) 79 - state.value = newState 87 + settings = rpc.querySettings() 88 + tags = rpc.queryTagsForActivity(activityId) 89 + 90 + state.value = mapState() 80 91 } 81 92 } 82 93 94 + private suspend fun startActivity() { 95 + val activityId = this@TagsViewModel.activityId ?: return 96 + currentActivitiesMediator.start( 97 + activityId = activityId, 98 + tags = selectedTags, 99 + ) 100 + effects.emit(Effect.OnComplete) 101 + } 102 + 103 + private fun mapState(): State { 104 + return tagsViewDataMapper.mapState( 105 + tags = tags, 106 + selectedTags = selectedTags, 107 + settings = settings, 108 + ) 109 + } 110 + 83 111 data class State( 84 - val tags: List<WearTag>, 85 - val selectedTags: List<WearTag>, 86 - val mode: TagSelectionMode, 112 + val listState: TagListState, 87 113 val settings: WearSettings, 88 114 ) { 89 115 companion object { 90 116 val Empty = State( 91 - tags = emptyList(), 92 - selectedTags = emptyList(), 93 - mode = TagSelectionMode.SINGLE, 117 + listState = TagListState.Empty(R.string.no_tags), 94 118 settings = WearSettings( 95 119 allowMultitasking = false, 96 120 showRecordTagSelection = false,
+4
wear/src/main/java/com/example/util/simpletimetracker/presentation/theme/Color.kt
··· 5 5 */ 6 6 package com.example.util.simpletimetracker.presentation.theme 7 7 8 + import androidx.compose.ui.graphics.Color 8 9 import androidx.wear.compose.material.Colors 10 + 11 + val ColorActive = Color(0xFF263238) 12 + val ColorInactive = Color(0xFF455A64) 9 13 10 14 internal val wearColors: Colors = Colors()
+7
wear/src/main/java/com/example/util/simpletimetracker/presentation/utils/Extensions.kt
··· 6 6 package com.example.util.simpletimetracker.presentation.utils 7 7 8 8 import android.annotation.SuppressLint 9 + import androidx.annotation.StringRes 9 10 import androidx.compose.runtime.Composable 10 11 import androidx.compose.runtime.LaunchedEffect 12 + import androidx.compose.ui.platform.LocalContext 11 13 import kotlinx.coroutines.flow.Flow 12 14 import kotlinx.coroutines.flow.collect 13 15 import kotlinx.coroutines.flow.onEach ··· 21 23 LaunchedEffect(key) { 22 24 onEach { action(it) }.collect() 23 25 } 26 + } 27 + 28 + @Composable 29 + fun getString(@StringRes stringResId: Int): String { 30 + return LocalContext.current.getString(stringResId) 24 31 }
-1
wear/src/main/res/values-es/strings.xml
··· 1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 <resources> 3 - <string name="submit_button_default_content_description">Enviar</string> 4 3 <string name="refresh_button_default_content_description">Actualizar</string> 5 4 <string name="no_tags">Sin etiquetas</string> 6 5 <string name="no_activities">Sin actividades</string>
+2 -1
wear/src/main/res/values/strings.xml
··· 1 1 <resources> 2 - <string name="submit_button_default_content_description">Submit</string> 3 2 <string name="refresh_button_default_content_description">Refresh</string> 4 3 <string name="no_tags">No tags</string> 5 4 <string name="no_activities">No activities</string> 5 + <string name="untagged">Untagged</string> 6 + <string name="save">Save</string> 6 7 </resources>