refactor: extract strings to be translatable

This commit is contained in:
Harsh Shandilya
2026-03-10 03:38:00 +05:30
parent c6cf566b13
commit 7c9b93fc3a
18 changed files with 202 additions and 72 deletions
@@ -12,7 +12,6 @@ import android.content.Intent
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.glance.GlanceComposable
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
@@ -28,8 +27,6 @@ import androidx.glance.layout.Alignment
import androidx.glance.layout.Box
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.padding
import androidx.glance.preview.ExperimentalGlancePreviewApi
import androidx.glance.preview.Preview
import androidx.glance.text.Text
import androidx.glance.text.TextAlign
import androidx.glance.text.TextStyle
@@ -37,6 +34,7 @@ import com.slack.eithernet.ApiResult
import dev.msfjarvis.claw.android.BuildConfig
import dev.msfjarvis.claw.android.ClawApplication
import dev.msfjarvis.claw.android.MainActivity
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.database.local.CachedRemotePost
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.UIPost
@@ -104,16 +102,20 @@ class HottestPostsWidget : GlanceAppWidget() {
.filter { post -> post.tags.none { tag -> filteredTags.contains(tag) } }
.toImmutableList()
}
provideContent { LobstersGlanceTheme { Content(filteredPosts) } }
provideContent { LobstersGlanceTheme { Content(context, filteredPosts) } }
}
override suspend fun providePreview(context: Context, widgetCategory: Int) {
provideContent { Content(samplePosts()) }
provideContent { Content(context, samplePosts()) }
}
@SuppressLint("ComposeUnstableReceiver")
@Composable
private fun Content(posts: ImmutableList<UIPost>, modifier: GlanceModifier = GlanceModifier) {
private fun Content(
context: Context,
posts: ImmutableList<UIPost>,
modifier: GlanceModifier = GlanceModifier,
) {
WidgetContainer(
"Hottest posts",
listContent = {
@@ -142,7 +144,7 @@ class HottestPostsWidget : GlanceAppWidget() {
),
) {
Text(
text = "See more posts",
text = context.getString(R.string.see_more_posts),
style =
TextStyle(color = GlanceTheme.colors.onPrimary, textAlign = TextAlign.Center),
)
@@ -153,13 +155,4 @@ class HottestPostsWidget : GlanceAppWidget() {
modifier = modifier,
)
}
@SuppressLint("VisibleForTests", "ComposeUnstableReceiver")
@OptIn(ExperimentalGlancePreviewApi::class)
@Preview
@GlanceComposable
@Composable
private fun Preview() {
Content(samplePosts())
}
}
@@ -36,6 +36,7 @@ import androidx.glance.text.TextStyle
import dev.msfjarvis.claw.android.BuildConfig
import dev.msfjarvis.claw.android.ClawApplication
import dev.msfjarvis.claw.android.MainActivity
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.model.UIPost
import dev.msfjarvis.claw.model.fromSavedPost
import kotlinx.collections.immutable.ImmutableList
@@ -58,8 +59,9 @@ class SavedPostsWidget : GlanceAppWidget() {
@SuppressLint("ComposeUnstableReceiver")
@Composable
private fun Content(posts: ImmutableList<UIPost>, modifier: GlanceModifier = GlanceModifier) {
val context = LocalContext.current
WidgetContainer(
title = "Saved posts",
title = context.getString(R.string.saved_posts),
listContent = {
items(posts) { post ->
Box(GlanceModifier.padding(horizontal = 16.dp, vertical = 4.dp)) { WidgetPostEntry(post) }
@@ -86,7 +88,7 @@ class SavedPostsWidget : GlanceAppWidget() {
),
) {
Text(
text = "See more posts",
text = context.getString(R.string.see_more_posts),
style =
TextStyle(color = GlanceTheme.colors.onPrimary, textAlign = TextAlign.Center),
)
@@ -6,6 +6,7 @@
*/
package dev.msfjarvis.claw.android.glance
import android.annotation.SuppressLint
import android.content.Intent
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@@ -32,6 +33,7 @@ import androidx.glance.text.TextStyle
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.model.UIPost
@SuppressLint("ArgInFormattedQuantityStringRes")
@Composable
@GlanceComposable
fun WidgetPostEntry(post: UIPost, modifier: GlanceModifier = GlanceModifier) {
@@ -71,7 +73,12 @@ fun WidgetPostEntry(post: UIPost, modifier: GlanceModifier = GlanceModifier) {
)
Image(
provider = ImageProvider(R.drawable.ic_comment),
contentDescription = "${post.commentCount} comments",
contentDescription =
context.resources.getQuantityString(
R.plurals.comments,
post.commentCount,
post.commentCount,
),
colorFilter = ColorFilter.tint(GlanceTheme.colors.onSecondaryContainer),
modifier =
GlanceModifier.clickable(commentsAction)
@@ -45,13 +45,13 @@ fun ClawAppBar(
IconButton(onClick = { if (popBackStack() == null) activity?.finish() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Go back to previous screen",
contentDescription = stringResource(R.string.go_back_to_previous_screen),
)
}
} else {
Icon(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "The app icon for Claw",
contentDescription = stringResource(R.string.the_app_icon_for_claw),
modifier = Modifier.size(48.dp),
)
}
@@ -64,10 +64,16 @@ fun ClawAppBar(
actions = {
if (isTopLevel) {
IconButton(onClick = { navigateTo(Search) }) {
Icon(imageVector = Icons.Filled.Search, contentDescription = "Search posts")
Icon(
imageVector = Icons.Filled.Search,
contentDescription = stringResource(R.string.search_posts),
)
}
IconButton(onClick = { navigateTo(Settings) }) {
Icon(imageVector = Icons.Filled.Tune, contentDescription = "Settings")
Icon(
imageVector = Icons.Filled.Tune,
contentDescription = stringResource(R.string.settings),
)
}
}
},
@@ -25,7 +25,9 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.ui.decorations.MonthHeader
import dev.msfjarvis.claw.model.UIPost
@@ -46,10 +48,13 @@ fun DatabasePosts(
Column(modifier = Modifier.align(Alignment.Center)) {
Icon(
imageVector = Icons.Filled.Inbox,
contentDescription = "Empty inbox icon",
contentDescription = stringResource(R.string.empty_inbox_icon),
modifier = Modifier.align(Alignment.CenterHorizontally).size(36.dp),
)
Text(text = "No saved posts", style = MaterialTheme.typography.headlineSmall)
Text(
text = stringResource(R.string.no_saved_posts),
style = MaterialTheme.typography.headlineSmall,
)
}
} else {
LazyColumn(state = listState, contentPadding = contentPadding) {
@@ -36,6 +36,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -49,6 +50,7 @@ import com.mikepenz.aboutlibraries.ui.compose.android.produceLibraries
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.hazeSource
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.android.ui.PostActions
import dev.msfjarvis.claw.android.ui.decorations.ClawAppBar
import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationBar
@@ -353,5 +355,5 @@ private fun popBackStack(backStack: MutableList<NavKey>): NavKey? {
@Composable
private fun Placeholder(modifier: Modifier = Modifier) {
Text(text = "No post selected", modifier = modifier)
Text(text = stringResource(R.string.no_post_selected), modifier = modifier)
}
@@ -39,8 +39,10 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.android.BuildConfig
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.ui.preview.ThemePreviews
import io.sentry.Sentry
@@ -70,10 +72,10 @@ fun SettingsScreen(
val coroutineScope = rememberCoroutineScope()
Column(modifier.padding(contentPadding)) {
// Data Management Section
SectionHeader(title = "Data Management")
SectionHeader(title = stringResource(R.string.data_management))
ListItem(
headlineContent = { Text("Data transfer") },
supportingContent = { Text("Import and export your saved posts") },
headlineContent = { Text(stringResource(R.string.data_transfer)) },
supportingContent = { Text(stringResource(R.string.import_and_export_your_saved_posts)) },
leadingContent = {
Icon(
imageVector = Icons.Filled.ImportExport,
@@ -98,10 +100,18 @@ fun SettingsScreen(
Spacer(modifier = Modifier.height(24.dp))
// App Information Section
SectionHeader(title = "App Information")
SectionHeader(title = stringResource(R.string.app_information))
ListItem(
headlineContent = { Text("Version") },
supportingContent = { Text("${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") },
headlineContent = { Text(stringResource(R.string.version)) },
supportingContent = {
Text(
stringResource(
R.string.version_placeholder,
BuildConfig.VERSION_NAME,
BuildConfig.VERSION_CODE,
)
)
},
leadingContent = {
Icon(
imageVector = Icons.Filled.Code,
@@ -111,8 +121,8 @@ fun SettingsScreen(
},
)
ListItem(
headlineContent = { Text("Saved posts") },
supportingContent = { Text("$savedPostsCount posts saved locally") },
headlineContent = { Text(stringResource(R.string.saved_posts)) },
supportingContent = { Text(stringResource(R.string.posts_saved_locally, savedPostsCount)) },
leadingContent = {
Icon(
imageVector = Icons.Filled.Bookmarks,
@@ -122,8 +132,8 @@ fun SettingsScreen(
},
)
ListItem(
headlineContent = { Text("Tag filtering") },
supportingContent = { Text("Filter posts by tags") },
headlineContent = { Text(stringResource(R.string.tag_filtering)) },
supportingContent = { Text(stringResource(R.string.filter_posts_by_tags)) },
leadingContent = {
Icon(
imageVector = Icons.Filled.FilterList,
@@ -137,10 +147,12 @@ fun SettingsScreen(
Spacer(modifier = Modifier.height(24.dp))
// About Section
SectionHeader(title = "About")
SectionHeader(title = stringResource(R.string.about))
ListItem(
headlineContent = { Text("Libraries") },
supportingContent = { Text("View open source libraries used in this app") },
headlineContent = { Text(stringResource(R.string.libraries)) },
supportingContent = {
Text(stringResource(R.string.view_open_source_libraries_used_in_this_))
},
leadingContent = {
Icon(
imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
@@ -151,8 +163,8 @@ fun SettingsScreen(
modifier = Modifier.clickable(onClick = openLibrariesScreen),
)
ListItem(
headlineContent = { Text("Source code") },
supportingContent = { Text("View the source code on GitHub") },
headlineContent = { Text(stringResource(R.string.source_code)) },
supportingContent = { Text(stringResource(R.string.view_the_source_code_on_github)) },
leadingContent = {
Icon(
imageVector = Icons.Filled.Code,
@@ -218,7 +230,7 @@ private fun ImportPosts(
shape = MaterialTheme.shapes.extraSmall,
modifier = modifier,
) {
Text(text = "Import")
Text(text = stringResource(R.string.import_action))
}
}
@@ -285,20 +297,23 @@ private inline fun ExportPosts(
shape = MaterialTheme.shapes.extraSmall,
modifier = modifier,
) {
Text(text = "Export")
Text(text = stringResource(R.string.export))
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
DropdownMenuItem(
text = { Text("JSON") },
text = { Text(stringResource(R.string.json)) },
onClick = {
expanded = false
jsonExportAction.launch("claw-export.json")
},
leadingIcon = {
Icon(imageVector = Icons.Filled.Code, contentDescription = "Export as JSON")
Icon(
imageVector = Icons.Filled.Code,
contentDescription = stringResource(R.string.export_as_json),
)
},
)
DropdownMenuItem(
text = { Text("Bookmarks") },
text = { Text(stringResource(R.string.bookmarks)) },
onClick = {
expanded = false
htmlExportAction.launch("claw-export.html")
@@ -306,7 +321,7 @@ private inline fun ExportPosts(
leadingIcon = {
Icon(
imageVector = Icons.Filled.Bookmarks,
contentDescription = "Export as browser bookmarks",
contentDescription = stringResource(R.string.export_as_browser_bookmarks),
)
},
)
+34 -1
View File
@@ -4,8 +4,41 @@
~ license that can be found in the LICENSE file or at
~ https://opensource.org/licenses/MIT.
-->
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">Claw</string>
<string name="saved_posts_widget_description">View your saved Lobsters posts</string>
<string name="hottest_posts_widget_description">View the hottest posts on Lobsters</string>
<string name="saved_posts">Saved posts</string>
<string name="see_more_posts">See more posts</string>
<plurals name="comments">
<item quantity="one">%1$d comment</item>
<item quantity="other">%1$d comments</item>
</plurals>
<string name="no_post_selected">No post selected</string>
<string name="data_management">Data Management</string>
<string name="data_transfer">Data transfer</string>
<string name="import_and_export_your_saved_posts">Import and export your saved posts</string>
<string name="app_information">App Information</string>
<string name="version">Version</string>
<string name="version_placeholder">%1$s (%2$s)</string>
<string name="posts_saved_locally">%1$s posts saved locally</string>
<string name="tag_filtering">Tag filtering</string>
<string name="filter_posts_by_tags">Filter posts by tags</string>
<string name="about">About</string>
<string name="libraries">Libraries</string>
<string name="view_open_source_libraries_used_in_this_">View open source libraries used in this app</string>
<string name="source_code">Source code</string>
<string name="view_the_source_code_on_github">View the source code on GitHub</string>
<string name="import_action">Import</string>
<string name="export">Export</string>
<string name="json">JSON</string>
<string name="export_as_json">Export as JSON</string>
<string name="bookmarks">Bookmarks</string>
<string name="export_as_browser_bookmarks">Export as browser bookmarks</string>
<string name="go_back_to_previous_screen">Go back to previous screen</string>
<string name="the_app_icon_for_claw">The app icon for Claw</string>
<string name="search_posts">Search posts</string>
<string name="settings">Settings</string>
<string name="empty_inbox_icon">Empty inbox icon</string>
<string name="no_saved_posts">No saved posts</string>
</resources>
@@ -27,11 +27,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.github.michaelbull.result.coroutines.runSuspendCatching
import com.github.michaelbull.result.onSuccess
import dev.msfjarvis.claw.common.R
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.posts.PostTitle
import dev.msfjarvis.claw.common.posts.Submitter
@@ -85,7 +87,7 @@ internal fun CommentsHeader(
"${if (post.userIsAuthor) "Authored" else "Submitted"} by ${post.submitter}"
),
avatarUrl = "https://lobste.rs/avatars/${post.submitter}-100.png",
contentDescription = "User avatar for ${post.submitter}",
contentDescription = stringResource(R.string.user_avatar_for, post.submitter),
modifier = Modifier.clickable { openUserProfile(post.submitter) },
)
}
@@ -41,6 +41,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
@@ -50,6 +51,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import dev.msfjarvis.claw.common.R
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.posts.Submitter
import dev.msfjarvis.claw.common.ui.ThemedRichText
@@ -115,7 +117,7 @@ internal fun CommentsPageInternal(
if (commentNodes.isNotEmpty()) {
item(key = "comments_header") {
Text(
text = "Comments",
text = stringResource(R.string.comments_label),
style = MaterialTheme.typography.labelLarge,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
)
@@ -131,7 +133,7 @@ internal fun CommentsPageInternal(
} else {
item {
Text(
text = "No Comments",
text = stringResource(R.string.no_comments),
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.fillMaxWidth().padding(16.dp),
fontWeight = FontWeight.Bold,
@@ -211,7 +213,7 @@ private fun CommentEntry(
if (commentNode.isPostAuthor) MaterialTheme.colorScheme.tertiary else null,
),
avatarUrl = "https://lobste.rs/avatars/${comment.user}-100.png",
contentDescription = "User avatar for ${comment.user}",
contentDescription = stringResource(R.string.user_avatar_for, comment.user),
modifier = Modifier.clickable { openUserProfile(comment.user) },
)
}
@@ -45,11 +45,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.common.R
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.ui.NetworkImage
import dev.msfjarvis.claw.common.ui.preview.ThemePreviews
@@ -124,7 +126,7 @@ fun PostDetails(
"${if (post.userIsAuthor) "Authored" else "Submitted"} by ${post.submitter}"
),
avatarUrl = "https://lobste.rs/avatars/${post.submitter}-100.png",
contentDescription = "User avatar for ${post.submitter}",
contentDescription = stringResource(R.string.user_avatar_for, post.submitter),
)
}
}
@@ -195,7 +197,7 @@ private fun CommentsButton(commentCount: Int, modifier: Modifier = Modifier) {
Icon(
imageVector = Icons.AutoMirrored.Filled.Comment,
tint = MaterialTheme.colorScheme.secondary,
contentDescription = "Open comments",
contentDescription = stringResource(R.string.open_comments),
modifier = Modifier.align(Alignment.Center).testTag("comments_button"),
)
Box(
@@ -14,21 +14,23 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.common.R
@Composable
fun TagExpirationTestControls(onTriggerCleanup: () -> Unit, modifier: Modifier = Modifier) {
Column(modifier = modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp)) {
Text(
text = "Debug Controls",
text = stringResource(R.string.debug_controls),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.error,
)
Button(onClick = onTriggerCleanup, modifier = Modifier.fillMaxWidth().padding(top = 8.dp)) {
Text("Force Cleanup Expired Tags Now")
Text(stringResource(R.string.force_cleanup_expired_tags_now))
}
Text(
text = "This will immediately run the cleanup worker to remove expired tag blocks",
text = stringResource(R.string.this_will_immediately_run_the_cleanup_wo),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 4.dp),
@@ -41,12 +41,14 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import dev.msfjarvis.claw.common.BuildConfig
import dev.msfjarvis.claw.common.NetworkState.Error
import dev.msfjarvis.claw.common.NetworkState.Loading
import dev.msfjarvis.claw.common.NetworkState.Success
import dev.msfjarvis.claw.common.R
import dev.msfjarvis.claw.common.ui.ProgressBar
import dev.msfjarvis.claw.model.Tag
import dev.zacsweers.metrox.viewmodel.metroViewModel
@@ -90,7 +92,7 @@ fun TagList(
selectedTagForDatePicker = null
}
) {
Text("Block Until")
Text(stringResource(R.string.block_until))
}
},
dismissButton = {
@@ -101,7 +103,7 @@ fun TagList(
selectedTagForDatePicker = null
}
) {
Text("Block Forever")
Text(stringResource(R.string.block_forever))
}
},
) {
@@ -118,7 +120,10 @@ fun TagList(
}
is Error -> {
Box(modifier = modifier.fillMaxSize().padding(contentPadding)) {
Text("Failed to load tags", modifier = Modifier.align(Alignment.Center))
Text(
stringResource(R.string.failed_to_load_tags),
modifier = Modifier.align(Alignment.Center),
)
}
}
is Success<*> -> {
@@ -130,7 +135,7 @@ fun TagList(
TagExpirationTestControls(onTriggerCleanup = { viewModel.triggerCleanupNow() })
}
Text(
text = "Posts with selected tags will be filtered out of hottest/newest/search feeds",
text = stringResource(R.string.posts_with_selected_tags_will_be_filtere),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground,
)
@@ -184,7 +189,8 @@ fun TagList(
if (isSelected && tagBlock != null) {
tagBlock.expirationMillis?.let { expirationMillis ->
Text(
text = "${formatDate(expirationMillis)}",
text =
stringResource(R.string.expirationmillis, formatDate(expirationMillis)),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -29,9 +29,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.common.R
// Taken from Chris Banes' amazing app Tivi
// https://github.com/chrisbanes/tivi/blob/836d596d74959f4235ca2395b5bbfdd6fd9c9a9e/ui/root/src/commonMain/kotlin/app/tivi/home/Home.kt#L173
@@ -78,17 +80,32 @@ private fun FloatingNavigationBarPreview() {
NavigationBarItem(
selected = true,
onClick = {},
icon = { Icon(imageVector = Icons.Filled.HeartBroken, contentDescription = "Home") },
icon = {
Icon(
imageVector = Icons.Filled.HeartBroken,
contentDescription = stringResource(R.string.home),
)
},
)
NavigationBarItem(
selected = true,
onClick = {},
icon = { Icon(imageVector = Icons.Filled.BarChart, contentDescription = "Home") },
icon = {
Icon(
imageVector = Icons.Filled.BarChart,
contentDescription = stringResource(R.string.home),
)
},
)
NavigationBarItem(
selected = true,
onClick = {},
icon = { Icon(imageVector = Icons.Filled.BrokenImage, contentDescription = "Home") },
icon = {
Icon(
imageVector = Icons.Filled.BrokenImage,
contentDescription = stringResource(R.string.home),
)
},
)
}
}
@@ -26,7 +26,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.common.R
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.ui.preview.ThemePreviews
import kotlinx.coroutines.launch
@@ -45,29 +47,30 @@ fun NetworkError(label: String, error: Throwable, modifier: Modifier = Modifier)
onClick = { showDialog = true },
modifier = Modifier.align(Alignment.CenterHorizontally),
) {
Text(text = "Show error")
Text(text = stringResource(R.string.show_error))
}
}
if (showDialog) {
val coroutineScope = rememberCoroutineScope()
val clipboard = LocalClipboard.current
val stacktraceStr = stringResource(R.string.stacktrace)
AlertDialog(
onDismissRequest = { showDialog = false },
confirmButton = {
Text(
text = "Copy stacktrace",
text = stringResource(R.string.copy_stacktrace),
modifier =
Modifier.clickable {
coroutineScope.launch {
clipboard.setClipEntry(
ClipEntry(ClipData.newPlainText("Stacktrace", error.stackTraceToString()))
ClipEntry(ClipData.newPlainText(stacktraceStr, error.stackTraceToString()))
)
}
showDialog = false
},
)
},
text = { Text(text = "${error.message}", style = MaterialTheme.typography.bodyLarge) },
text = { Text(text = error.message.orEmpty(), style = MaterialTheme.typography.bodyLarge) },
)
}
}
@@ -31,9 +31,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.common.R
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.ui.preview.DevicePreviews
import dev.msfjarvis.claw.common.ui.preview.ThemePreviews
@@ -51,10 +53,13 @@ fun SearchBar(
onValueChange = onValueChange,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
textStyle = MaterialTheme.typography.bodyLarge,
placeholder = { Text(text = "Search") },
placeholder = { Text(text = stringResource(R.string.search)) },
trailingIcon = {
IconButton(onClick = { onSearch(value) }) {
Icon(imageVector = Icons.Outlined.Search, contentDescription = "Search")
Icon(
imageVector = Icons.Outlined.Search,
contentDescription = stringResource(R.string.search),
)
}
},
keyboardActions = KeyboardActions(onSearch = { onSearch(value) }),
@@ -26,12 +26,14 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.common.NetworkState.Error
import dev.msfjarvis.claw.common.NetworkState.Loading
import dev.msfjarvis.claw.common.NetworkState.Success
import dev.msfjarvis.claw.common.R
import dev.msfjarvis.claw.common.ui.NetworkError
import dev.msfjarvis.claw.common.ui.NetworkImage
import dev.msfjarvis.claw.common.ui.ProgressBar
@@ -93,7 +95,7 @@ private fun UserProfileInternal(
NetworkImage(
url = "https://lobste.rs/${user.avatarUrl}",
placeholder = Icons.Filled.AccountCircle,
contentDescription = "Avatar of ${user.username}",
contentDescription = stringResource(R.string.avatar_of, user.username),
modifier = Modifier.requiredSize(120.dp).clip(CircleShape),
)
Text(text = user.username, style = MaterialTheme.typography.displaySmall)
+26
View File
@@ -0,0 +1,26 @@
<!--
~ Copyright © Harsh Shandilya.
~ Use of this source code is governed by an MIT-style
~ license that can be found in the LICENSE file or at
~ https://opensource.org/licenses/MIT.
-->
<resources>
<string name="block_until">Block Until</string>
<string name="block_forever">Block Forever</string>
<string name="failed_to_load_tags">Failed to load tags</string>
<string name="posts_with_selected_tags_will_be_filtere">Posts with selected tags will be filtered out of hottest/newest/search feeds</string>
<string name="expirationmillis"> • %1$s</string>
<string name="debug_controls">Debug Controls</string>
<string name="force_cleanup_expired_tags_now">Force Cleanup Expired Tags Now</string>
<string name="this_will_immediately_run_the_cleanup_wo">This will immediately run the cleanup worker to remove expired tag blocks</string>
<string name="user_avatar_for">User avatar for %1$s</string>
<string name="comments_label">Comments</string>
<string name="no_comments">No Comments</string>
<string name="open_comments">Open comments</string>
<string name="avatar_of">Avatar of %1$s</string>
<string name="home">Home</string>
<string name="show_error">Show error</string>
<string name="copy_stacktrace">Copy stacktrace</string>
<string name="stacktrace">Stacktrace</string>
<string name="search">Search</string>
</resources>