refactor: extract strings to be translatable
This commit is contained in:
@@ -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) {
|
||||
|
||||
+3
-1
@@ -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),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user