extract most strings to strings.xml and add arabic language to strings.xml file

This commit is contained in:
Rashed
2023-02-28 21:20:49 +03:00
parent 3780ed001a
commit e6afa7fb9a
47 changed files with 714 additions and 241 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" /> <bytecodeTargetLevel target="17" />
</component> </component>
</project> </project>
+3
View File
@@ -5,6 +5,9 @@
<option name="composableFile" value="true" /> <option name="composableFile" value="true" />
<option name="previewFile" value="true" /> <option name="previewFile" value="true" />
</inspection_tool> </inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true"> <inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" /> <option name="composableFile" value="true" />
<option name="previewFile" value="true" /> <option name="previewFile" value="true" />
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">
@@ -88,7 +88,7 @@ class Relay(
"NOTICE" -> listeners.forEach { "NOTICE" -> listeners.forEach {
//Log.w("Relay", "Relay onNotice $url, $channel") //Log.w("Relay", "Relay onNotice $url, $channel")
// "channel" being the second string in the string array ... // "channel" being the second string in the string array ...
it.onError(this@Relay, channel, Error("Relay sent notice: $channel")) it.onError(this@Relay, channel, Error("Relay sent notice: " + channel))
} }
"OK" -> listeners.forEach { "OK" -> listeners.forEach {
//Log.w("Relay", "Relay onOK $url, $channel") //Log.w("Relay", "Relay onOK $url, $channel")
@@ -10,6 +10,7 @@ import android.provider.MediaStore
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.net.toUri import androidx.core.net.toUri
import java.io.File import java.io.File
import com.vitorpamplona.amethyst.R
import okhttp3.* import okhttp3.*
import okio.BufferedSource import okio.BufferedSource
import okio.IOException import okio.IOException
@@ -49,7 +50,7 @@ object ImageSaver {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentType = response.header("Content-Type") val contentType = response.header("Content-Type")
checkNotNull(contentType) { checkNotNull(contentType) {
"Can't find out the content type" context.getString(R.string.can_t_find_out_the_content_type)
} }
saveContentQ( saveContentQ(
@@ -57,6 +58,7 @@ object ImageSaver {
contentType = contentType, contentType = contentType,
contentSource = response.body.source(), contentSource = response.body.source(),
contentResolver = context.contentResolver, contentResolver = context.contentResolver,
context = context
) )
} else { } else {
saveContentDefault( saveContentDefault(
@@ -80,6 +82,7 @@ object ImageSaver {
contentType: String, contentType: String,
contentSource: BufferedSource, contentSource: BufferedSource,
contentResolver: ContentResolver, contentResolver: ContentResolver,
context: Context
) { ) {
val contentValues = ContentValues().apply { val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, displayName) put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
@@ -93,13 +96,13 @@ object ImageSaver {
val uri = val uri =
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
checkNotNull(uri) { checkNotNull(uri) {
"Can't insert the new content" context.getString(R.string.can_t_insert_the_new_content)
} }
try { try {
val outputStream = contentResolver.openOutputStream(uri) val outputStream = contentResolver.openOutputStream(uri)
checkNotNull(outputStream) { checkNotNull(outputStream) {
"Can't open the content output stream" context.getString(R.string.can_t_open_the_content_output_stream)
} }
outputStream.use { outputStream.use {
@@ -1,6 +1,7 @@
package com.vitorpamplona.amethyst.ui.actions package com.vitorpamplona.amethyst.ui.actions
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context
import android.net.Uri import android.net.Uri
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import java.io.IOException import java.io.IOException
@@ -8,6 +9,7 @@ import java.util.UUID
import okhttp3.Call import okhttp3.Call
import okhttp3.Callback import okhttp3.Callback
import okhttp3.MediaType import okhttp3.MediaType
import com.vitorpamplona.amethyst.R
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody import okhttp3.MultipartBody
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@@ -23,6 +25,7 @@ object ImageUploader {
contentResolver: ContentResolver, contentResolver: ContentResolver,
onSuccess: (String) -> Unit, onSuccess: (String) -> Unit,
onError: (Throwable) -> Unit, onError: (Throwable) -> Unit,
context : Context
) { ) {
val contentType = contentResolver.getType(uri) val contentType = contentResolver.getType(uri)
@@ -40,7 +43,7 @@ object ImageUploader {
override fun writeTo(sink: BufferedSink) { override fun writeTo(sink: BufferedSink) {
val imageInputStream = contentResolver.openInputStream(uri) val imageInputStream = contentResolver.openInputStream(uri)
checkNotNull(imageInputStream) { checkNotNull(imageInputStream) {
"Can't open the image input stream" context.getString(R.string.can_t_open_the_image_input_stream)
} }
imageInputStream.source().use(sink::writeAll) imageInputStream.source().use(sink::writeAll)
@@ -63,7 +66,7 @@ object ImageUploader {
val tree = jacksonObjectMapper().readTree(body.string()) val tree = jacksonObjectMapper().readTree(body.string())
val url = tree?.get("data")?.get("link")?.asText() val url = tree?.get("data")?.get("link")?.asText()
checkNotNull(url) { checkNotNull(url) {
"There must be an uploaded image URL in the response" context.getString(R.string.there_must_be_an_uploaded_image_url_in_the_response)
} }
onSuccess(url) onSuccess(url)
@@ -17,12 +17,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.Channel
@@ -67,13 +70,13 @@ fun NewChannelView(onClose: () -> Unit, account: Account, channel: Channel? = nu
Spacer(modifier = Modifier.height(15.dp)) Spacer(modifier = Modifier.height(15.dp))
OutlinedTextField( OutlinedTextField(
label = { Text(text = "Channel Name") }, label = { Text(text = stringResource(R.string.channel_name)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = postViewModel.channelName.value, value = postViewModel.channelName.value,
onValueChange = { postViewModel.channelName.value = it }, onValueChange = { postViewModel.channelName.value = it },
placeholder = { placeholder = {
Text( Text(
text = "My Awesome Group", text = stringResource(R.string.my_awesome_group),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
}, },
@@ -86,7 +89,7 @@ fun NewChannelView(onClose: () -> Unit, account: Account, channel: Channel? = nu
Spacer(modifier = Modifier.height(15.dp)) Spacer(modifier = Modifier.height(15.dp))
OutlinedTextField( OutlinedTextField(
label = { Text(text = "Picture Url") }, label = { Text(text = stringResource(R.string.picture_url)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = postViewModel.channelPicture.value, value = postViewModel.channelPicture.value,
onValueChange = { postViewModel.channelPicture.value = it }, onValueChange = { postViewModel.channelPicture.value = it },
@@ -101,13 +104,13 @@ fun NewChannelView(onClose: () -> Unit, account: Account, channel: Channel? = nu
Spacer(modifier = Modifier.height(15.dp)) Spacer(modifier = Modifier.height(15.dp))
OutlinedTextField( OutlinedTextField(
label = { Text(text = "Description") }, label = { Text(text = stringResource(R.string.description)) },
modifier = Modifier.fillMaxWidth().height(100.dp), modifier = Modifier.fillMaxWidth().height(100.dp),
value = postViewModel.channelDescription.value, value = postViewModel.channelDescription.value,
onValueChange = { postViewModel.channelDescription.value = it }, onValueChange = { postViewModel.channelDescription.value = it },
placeholder = { placeholder = {
Text( Text(
text = "About us.. ", text = stringResource(R.string.about_us),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
}, },
@@ -25,6 +25,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -136,7 +137,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
}, },
placeholder = { placeholder = {
Text( Text(
text = "What's on your mind?", text = stringResource(R.string.what_s_on_your_mind),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
}, },
@@ -221,7 +222,7 @@ fun CloseButton(onCancel: () -> Unit) {
backgroundColor = Color.Gray backgroundColor = Color.Gray
) )
) { ) {
Text(text = "Cancel", color = Color.White) Text(text = stringResource(R.string.cancel), color = Color.White)
} }
} }
@@ -240,7 +241,7 @@ fun PostButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier =
backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray
) )
) { ) {
Text(text = "Post", color = Color.White) Text(text = stringResource(R.string.post), color = Color.White)
} }
} }
@@ -259,7 +260,7 @@ fun SaveButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier =
backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray
) )
) { ) {
Text(text = "Save", color = Color.White) Text(text = stringResource(R.string.save), color = Color.White)
} }
} }
@@ -278,7 +279,7 @@ fun CreateButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier
backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray
) )
) { ) {
Text(text = "Create", color = Color.White) Text(text = stringResource(R.string.create), color = Color.White)
} }
} }
@@ -14,6 +14,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.parseDirtyWordForKey import com.vitorpamplona.amethyst.model.parseDirtyWordForKey
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.components.isValidURL import com.vitorpamplona.amethyst.ui.components.isValidURL
import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@@ -133,9 +134,10 @@ class NewPostViewModel: ViewModel() {
onError = { onError = {
isUploadingImage = false isUploadingImage = false
viewModelScope.launch { viewModelScope.launch {
imageUploadingError.emit("Failed to upload the image") imageUploadingError.emit(context.getString(R.string.failed_to_upload_the_image))
}
} }
},
context = context
) )
} }
@@ -41,6 +41,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -141,7 +142,7 @@ fun ServerConfigHeader() {
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Text( Text(
text = "Relay Address", text = stringResource(R.string.relay_address),
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@@ -154,7 +155,7 @@ fun ServerConfigHeader() {
Spacer(modifier = Modifier.size(25.dp)) Spacer(modifier = Modifier.size(25.dp))
Text( Text(
text = "Posts", text = stringResource(R.string.posts),
maxLines = 1, maxLines = 1,
fontSize = 14.sp, fontSize = 14.sp,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
@@ -164,7 +165,7 @@ fun ServerConfigHeader() {
Spacer(modifier = Modifier.size(10.dp)) Spacer(modifier = Modifier.size(10.dp))
Text( Text(
text = "Posts", text = stringResource(id = R.string.posts),
maxLines = 1, maxLines = 1,
fontSize = 14.sp, fontSize = 14.sp,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
@@ -174,7 +175,7 @@ fun ServerConfigHeader() {
Spacer(modifier = Modifier.size(10.dp)) Spacer(modifier = Modifier.size(10.dp))
Text( Text(
text = "Errors", text = stringResource(R.string.errors),
maxLines = 1, maxLines = 1,
fontSize = 14.sp, fontSize = 14.sp,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
@@ -227,7 +228,9 @@ fun ServerConfig(
Icon( Icon(
imageVector = Icons.Default.Cancel, imageVector = Icons.Default.Cancel,
null, null,
modifier = Modifier.padding(end = 5.dp).size(15.dp), modifier = Modifier
.padding(end = 5.dp)
.size(15.dp),
tint = Color.Red tint = Color.Red
) )
} }
@@ -252,8 +255,10 @@ fun ServerConfig(
) { ) {
Icon( Icon(
painterResource(R.drawable.ic_home), painterResource(R.drawable.ic_home),
"Home Feed", stringResource(R.string.home_feed),
modifier = Modifier.padding(end = 5.dp).size(15.dp), modifier = Modifier
.padding(end = 5.dp)
.size(15.dp),
tint = if (item.feedTypes.contains(FeedType.FOLLOWS)) Color.Green else MaterialTheme.colors.onSurface.copy( tint = if (item.feedTypes.contains(FeedType.FOLLOWS)) Color.Green else MaterialTheme.colors.onSurface.copy(
alpha = 0.32f alpha = 0.32f
) )
@@ -265,8 +270,10 @@ fun ServerConfig(
) { ) {
Icon( Icon(
painterResource(R.drawable.ic_dm), painterResource(R.drawable.ic_dm),
"Private Message Feed", stringResource(R.string.private_message_feed),
modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), modifier = Modifier
.padding(horizontal = 5.dp)
.size(15.dp),
tint = if (item.feedTypes.contains(FeedType.PRIVATE_DMS)) Color.Green else MaterialTheme.colors.onSurface.copy( tint = if (item.feedTypes.contains(FeedType.PRIVATE_DMS)) Color.Green else MaterialTheme.colors.onSurface.copy(
alpha = 0.32f alpha = 0.32f
) )
@@ -278,8 +285,10 @@ fun ServerConfig(
) { ) {
Icon( Icon(
imageVector = Icons.Default.Groups, imageVector = Icons.Default.Groups,
"Public Chat Feed", stringResource(R.string.public_chat_feed),
modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), modifier = Modifier
.padding(horizontal = 5.dp)
.size(15.dp),
tint = if (item.feedTypes.contains(FeedType.PUBLIC_CHATS)) Color.Green else MaterialTheme.colors.onSurface.copy( tint = if (item.feedTypes.contains(FeedType.PUBLIC_CHATS)) Color.Green else MaterialTheme.colors.onSurface.copy(
alpha = 0.32f alpha = 0.32f
) )
@@ -291,8 +300,10 @@ fun ServerConfig(
) { ) {
Icon( Icon(
imageVector = Icons.Default.Public, imageVector = Icons.Default.Public,
"Global Feed", stringResource(R.string.global_feed),
modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), modifier = Modifier
.padding(horizontal = 5.dp)
.size(15.dp),
tint = if (item.feedTypes.contains(FeedType.GLOBAL)) Color.Green else MaterialTheme.colors.onSurface.copy( tint = if (item.feedTypes.contains(FeedType.GLOBAL)) Color.Green else MaterialTheme.colors.onSurface.copy(
alpha = 0.32f alpha = 0.32f
) )
@@ -310,7 +321,9 @@ fun ServerConfig(
Icon( Icon(
imageVector = Icons.Default.Download, imageVector = Icons.Default.Download,
null, null,
modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), modifier = Modifier
.padding(horizontal = 5.dp)
.size(15.dp),
tint = if (item.read) Color.Green else MaterialTheme.colors.onSurface.copy( tint = if (item.read) Color.Green else MaterialTheme.colors.onSurface.copy(
alpha = 0.32f alpha = 0.32f
) )
@@ -332,7 +345,9 @@ fun ServerConfig(
Icon( Icon(
imageVector = Icons.Default.Upload, imageVector = Icons.Default.Upload,
null, null,
modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), modifier = Modifier
.padding(horizontal = 5.dp)
.size(15.dp),
tint = if (item.write) Color.Green else MaterialTheme.colors.onSurface.copy( tint = if (item.write) Color.Green else MaterialTheme.colors.onSurface.copy(
alpha = 0.32f alpha = 0.32f
) )
@@ -350,7 +365,9 @@ fun ServerConfig(
Icon( Icon(
imageVector = Icons.Default.SyncProblem, imageVector = Icons.Default.SyncProblem,
null, null,
modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), modifier = Modifier
.padding(horizontal = 5.dp)
.size(15.dp),
tint = if (item.errorCount > 0) Color.Yellow else Color.Green tint = if (item.errorCount > 0) Color.Yellow else Color.Green
) )
@@ -396,7 +413,7 @@ fun EditableServerConfig(relayToAdd: String, onNewRelay: (RelaySetupInfo) -> Uni
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
OutlinedTextField( OutlinedTextField(
label = { Text(text = "Add a Relay") }, label = { Text(text = stringResource(R.string.add_a_relay)) },
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
value = url, value = url,
onValueChange = { url = it }, onValueChange = { url = it },
@@ -449,7 +466,7 @@ fun EditableServerConfig(relayToAdd: String, onNewRelay: (RelaySetupInfo) -> Uni
backgroundColor = if (url.isNotBlank()) MaterialTheme.colors.primary else MaterialTheme.colors.onSurface.copy(alpha = 0.32f) backgroundColor = if (url.isNotBlank()) MaterialTheme.colors.primary else MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
) { ) {
Text(text = "Add", color = Color.White) Text(text = stringResource(id = R.string.add), color = Color.White)
} }
} }
@@ -17,11 +17,13 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class)
@@ -69,13 +71,13 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Row(modifier = Modifier.fillMaxWidth(1f), verticalAlignment = Alignment.CenterVertically) { Row(modifier = Modifier.fillMaxWidth(1f), verticalAlignment = Alignment.CenterVertically) {
OutlinedTextField( OutlinedTextField(
label = { Text(text = "Display Name") }, label = { Text(text = stringResource(R.string.display_name)) },
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
value = postViewModel.displayName.value, value = postViewModel.displayName.value,
onValueChange = { postViewModel.displayName.value = it }, onValueChange = { postViewModel.displayName.value = it },
placeholder = { placeholder = {
Text( Text(
text = "My display name", text = stringResource(R.string.my_display_name),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
}, },
@@ -88,13 +90,13 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Text("@", Modifier.padding(5.dp)) Text("@", Modifier.padding(5.dp))
OutlinedTextField( OutlinedTextField(
label = { Text(text = "Username") }, label = { Text(text = stringResource(R.string.username)) },
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
value = postViewModel.userName.value, value = postViewModel.userName.value,
onValueChange = { postViewModel.userName.value = it }, onValueChange = { postViewModel.userName.value = it },
placeholder = { placeholder = {
Text( Text(
text = "My username", text = stringResource(R.string.my_username),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
}, },
@@ -105,7 +107,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField( OutlinedTextField(
label = { Text(text = "About me") }, label = { Text(text = stringResource(R.string.about_me)) },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(100.dp), .height(100.dp),
@@ -113,7 +115,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
onValueChange = { postViewModel.about.value = it }, onValueChange = { postViewModel.about.value = it },
placeholder = { placeholder = {
Text( Text(
text = "About me", text = stringResource(id = R.string.about_me),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
}, },
@@ -126,7 +128,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField( OutlinedTextField(
label = { Text(text = "Avatar URL") }, label = { Text(text = stringResource(R.string.avatar_url)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = postViewModel.picture.value, value = postViewModel.picture.value,
onValueChange = { postViewModel.picture.value = it }, onValueChange = { postViewModel.picture.value = it },
@@ -142,7 +144,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField( OutlinedTextField(
label = { Text(text = "Banner URL") }, label = { Text(text = stringResource(R.string.banner_url)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = postViewModel.banner.value, value = postViewModel.banner.value,
onValueChange = { postViewModel.banner.value = it }, onValueChange = { postViewModel.banner.value = it },
@@ -158,7 +160,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField( OutlinedTextField(
label = { Text(text = "Website URL") }, label = { Text(text = stringResource(R.string.website_url)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = postViewModel.website.value, value = postViewModel.website.value,
onValueChange = { postViewModel.website.value = it }, onValueChange = { postViewModel.website.value = it },
@@ -174,7 +176,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField( OutlinedTextField(
label = { Text(text = "NIP-05") }, label = { Text(text = stringResource(R.string.nip_05)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = postViewModel.nip05.value, value = postViewModel.nip05.value,
onValueChange = { postViewModel.nip05.value = it }, onValueChange = { postViewModel.nip05.value = it },
@@ -189,7 +191,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField( OutlinedTextField(
label = { Text(text = "LN Address") }, label = { Text(text = stringResource(R.string.ln_address)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = postViewModel.lnAddress.value, value = postViewModel.lnAddress.value,
onValueChange = { postViewModel.lnAddress.value = it }, onValueChange = { postViewModel.lnAddress.value = it },
@@ -205,13 +207,13 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField( OutlinedTextField(
label = { Text(text = "LN URL (outdated)") }, label = { Text(text = stringResource(R.string.ln_url_outdated)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = postViewModel.lnURL.value, value = postViewModel.lnURL.value,
onValueChange = { postViewModel.lnURL.value = it }, onValueChange = { postViewModel.lnURL.value = it },
placeholder = { placeholder = {
Text( Text(
text = "LNURL...", text = stringResource(R.string.lnurl),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
}, },
@@ -11,10 +11,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
import com.vitorpamplona.amethyst.R
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
@@ -29,6 +31,7 @@ fun SaveToGallery(url: String) {
val localContext = LocalContext.current val localContext = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
fun saveImage() { fun saveImage() {
ImageSaver.saveImage( ImageSaver.saveImage(
context = localContext, context = localContext,
@@ -37,7 +40,7 @@ fun SaveToGallery(url: String) {
scope.launch { scope.launch {
Toast.makeText( Toast.makeText(
localContext, localContext,
"Image saved to the gallery", localContext.getString(R.string.image_saved_to_the_gallery),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
) )
.show() .show()
@@ -47,7 +50,7 @@ fun SaveToGallery(url: String) {
scope.launch { scope.launch {
Toast.makeText( Toast.makeText(
localContext, localContext,
"Failed to save the image", localContext.getString(R.string.failed_to_save_the_image),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
) )
.show() .show()
@@ -78,6 +81,6 @@ fun SaveToGallery(url: String) {
backgroundColor = Color.Gray backgroundColor = Color.Gray
) )
) { ) {
Text(text = "Save", color = Color.White) Text(text = stringResource(id = R.string.save), color = Color.White)
} }
} }
@@ -12,10 +12,12 @@ import androidx.compose.material.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
import com.vitorpamplona.amethyst.R
@OptIn(ExperimentalPermissionsApi::class) @OptIn(ExperimentalPermissionsApi::class)
@Composable @Composable
@@ -54,9 +56,9 @@ fun UploadFromGallery(
} }
) { ) {
if (!isUploading) { if (!isUploading) {
Text("Upload Image") Text(stringResource(R.string.upload_image))
} else { } else {
Text("Uploading…") Text(stringResource(R.string.uploading))
} }
} }
} }
@@ -68,9 +70,9 @@ fun UploadFromGallery(
enabled = !isUploading, enabled = !isUploading,
) { ) {
if (!isUploading) { if (!isUploading) {
Text("Upload Image") Text(stringResource(R.string.upload_image))
} else { } else {
Text("Uploading…") Text(stringResource(R.string.uploading))
} }
} }
} }
@@ -16,6 +16,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.actions.NewChannelView import com.vitorpamplona.amethyst.ui.actions.NewChannelView
@@ -38,7 +40,7 @@ fun NewChannelButton(account: Account) {
) { ) {
Icon( Icon(
imageVector = Icons.Outlined.Add, imageVector = Icons.Outlined.Add,
contentDescription = "New Channel", contentDescription = stringResource(R.string.new_channel),
modifier = Modifier.size(26.dp), modifier = Modifier.size(26.dp),
tint = Color.White tint = Color.White
) )
@@ -25,6 +25,8 @@ import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.R
import androidx.compose.ui.res.stringResource
@Composable @Composable
fun ExpandableRichTextViewer( fun ExpandableRichTextViewer(
@@ -67,7 +69,7 @@ fun ExpandableRichTextViewer(
), ),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp) contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
) { ) {
Text(text = "Show More", color = Color.White) Text(text = stringResource(R.string.show_more), color = Color.White)
} }
} }
} }
@@ -22,6 +22,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -68,7 +69,7 @@ fun InvoicePreview(lnInvoice: String) {
) )
Text( Text(
text = "Lightning Invoice", text = stringResource(R.string.lightning_invoice),
fontSize = 20.sp, fontSize = 20.sp,
fontWeight = FontWeight.W500, fontWeight = FontWeight.W500,
modifier = Modifier.padding(start = 10.dp) modifier = Modifier.padding(start = 10.dp)
@@ -79,7 +80,9 @@ fun InvoicePreview(lnInvoice: String) {
amount?.let { amount?.let {
Text( Text(
text = "${NumberFormat.getInstance().format(amount)} sats", text = "${
NumberFormat.getInstance().format(amount)
} ${stringResource(id = R.string.sats)}",
fontSize = 25.sp, fontSize = 25.sp,
fontWeight = FontWeight.W500, fontWeight = FontWeight.W500,
modifier = Modifier modifier = Modifier
@@ -90,7 +93,9 @@ fun InvoicePreview(lnInvoice: String) {
Button( Button(
modifier = Modifier.fillMaxWidth().padding(vertical = 10.dp), modifier = Modifier
.fillMaxWidth()
.padding(vertical = 10.dp),
onClick = { onClick = {
runCatching { runCatching {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("lightning:$lnInvoice")) val intent = Intent(Intent.ACTION_VIEW, Uri.parse("lightning:$lnInvoice"))
@@ -102,7 +107,7 @@ fun InvoicePreview(lnInvoice: String) {
backgroundColor = MaterialTheme.colors.primary backgroundColor = MaterialTheme.colors.primary
) )
) { ) {
Text(text = "Pay", color = Color.White, fontSize = 20.sp) Text(text = stringResource(R.string.pay), color = Color.White, fontSize = 20.sp)
} }
} }
} }
@@ -30,6 +30,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
@@ -73,7 +74,7 @@ fun InvoiceRequest(lud16: String, toUserPubKeyHex: String, account: Account, onC
) )
Text( Text(
text = "Lightning Tips", text = stringResource(R.string.lightning_tips),
fontSize = 20.sp, fontSize = 20.sp,
fontWeight = FontWeight.W500, fontWeight = FontWeight.W500,
modifier = Modifier.padding(start = 10.dp) modifier = Modifier.padding(start = 10.dp)
@@ -86,13 +87,13 @@ fun InvoiceRequest(lud16: String, toUserPubKeyHex: String, account: Account, onC
var amount by remember { mutableStateOf(1000L) } var amount by remember { mutableStateOf(1000L) }
OutlinedTextField( OutlinedTextField(
label = { Text(text = "Note to Receiver") }, label = { Text(text = stringResource(R.string.note_to_receiver)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = message, value = message,
onValueChange = { message = it }, onValueChange = { message = it },
placeholder = { placeholder = {
Text( Text(
text = "Thank you so much!", text = stringResource(R.string.thank_you_so_much),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
}, },
@@ -103,7 +104,7 @@ fun InvoiceRequest(lud16: String, toUserPubKeyHex: String, account: Account, onC
) )
OutlinedTextField( OutlinedTextField(
label = { Text(text = "Amount in Sats") }, label = { Text(text = stringResource(R.string.amount_in_sats)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = amount.toString(), value = amount.toString(),
onValueChange = { onValueChange = {
@@ -152,7 +153,7 @@ fun InvoiceRequest(lud16: String, toUserPubKeyHex: String, account: Account, onC
backgroundColor = MaterialTheme.colors.primary backgroundColor = MaterialTheme.colors.primary
) )
) { ) {
Text(text = "Send Sats", color = Color.White, fontSize = 20.sp) Text(text = stringResource(R.string.send_sats), color = Color.White, fontSize = 20.sp)
} }
} }
} }
@@ -27,6 +27,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -34,6 +35,7 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.os.ConfigurationCompat import androidx.core.os.ConfigurationCompat
import androidx.navigation.NavController import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService
import com.vitorpamplona.amethyst.service.lang.ResultOrError import com.vitorpamplona.amethyst.service.lang.ResultOrError
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -110,17 +112,17 @@ fun TranslateableRichTextViewer(
val annotatedTranslationString = buildAnnotatedString { val annotatedTranslationString = buildAnnotatedString {
withStyle(clickableTextStyle) { withStyle(clickableTextStyle) {
pushStringAnnotation("langSettings", true.toString()) pushStringAnnotation("langSettings", true.toString())
append("Auto") append(stringResource(R.string.auto))
} }
append("-translated from ") append("-${stringResource(R.string.translated_from)} ")
withStyle(clickableTextStyle) { withStyle(clickableTextStyle) {
pushStringAnnotation("showOriginal", true.toString()) pushStringAnnotation("showOriginal", true.toString())
append(Locale(source).displayName) append(Locale(source).displayName)
} }
append(" to ") append(" ${stringResource(R.string.to)} ")
withStyle(clickableTextStyle) { withStyle(clickableTextStyle) {
pushStringAnnotation("showOriginal", false.toString()) pushStringAnnotation("showOriginal", false.toString())
@@ -163,7 +165,7 @@ fun TranslateableRichTextViewer(
Spacer(modifier = Modifier.size(10.dp)) Spacer(modifier = Modifier.size(10.dp))
Text("Never translate from ${Locale(source).displayName}") Text(stringResource(R.string.never_translate_from) + "${Locale(source).displayName}")
} }
Divider() Divider()
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
@@ -181,7 +183,14 @@ fun TranslateableRichTextViewer(
Spacer(modifier = Modifier.size(10.dp)) Spacer(modifier = Modifier.size(10.dp))
Text("Show in ${Locale(source).displayName} first") // TODO : Rashed translate this
Text(
"${stringResource(R.string.show_in)} ${Locale(source).displayName} ${
stringResource(
R.string.first
)
}"
)
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.prefer(source, target, target) accountViewModel.prefer(source, target, target)
@@ -198,7 +207,7 @@ fun TranslateableRichTextViewer(
Spacer(modifier = Modifier.size(10.dp)) Spacer(modifier = Modifier.size(10.dp))
Text("Show in ${Locale(target).displayName} first") Text("${stringResource(id = R.string.show_in)} ${Locale(target).displayName} ${R.string.first}")
} }
Divider() Divider()
@@ -221,7 +230,7 @@ fun TranslateableRichTextViewer(
Spacer(modifier = Modifier.size(10.dp)) Spacer(modifier = Modifier.size(10.dp))
Text("Always translate to ${lang.displayName}") Text("${stringResource(R.string.always_translate_to)}${lang.displayName}")
} }
} }
} }
@@ -8,8 +8,10 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.baha.url.preview.IUrlPreviewCallback import com.baha.url.preview.IUrlPreviewCallback
import com.baha.url.preview.UrlInfoItem import com.baha.url.preview.UrlInfoItem
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -18,7 +20,7 @@ import kotlinx.coroutines.withContext
@Composable @Composable
fun UrlPreview(url: String, urlText: String, showUrlIfError: Boolean = true) { fun UrlPreview(url: String, urlText: String, showUrlIfError: Boolean = true) {
var urlPreviewState by remember { mutableStateOf<UrlPreviewState>(UrlPreviewState.Loading) } var urlPreviewState by remember { mutableStateOf<UrlPreviewState>(UrlPreviewState.Loading) }
var context = LocalContext.current
// Doesn't use a viewModel because of viewModel reusing issues (too many UrlPreview are created). // Doesn't use a viewModel because of viewModel reusing issues (too many UrlPreview are created).
LaunchedEffect(url) { LaunchedEffect(url) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
@@ -31,7 +33,7 @@ fun UrlPreview(url: String, urlText: String, showUrlIfError: Boolean = true) {
} }
override fun onFailed(throwable: Throwable) { override fun onFailed(throwable: Throwable) {
urlPreviewState = UrlPreviewState.Error("Error parsing preview for ${url}: ${throwable.message}") urlPreviewState = UrlPreviewState.Error(context.getString(R.string.error_parsing_preview_for, url, throwable.message))
} }
}) })
} }
@@ -15,10 +15,12 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.baha.url.preview.UrlInfoItem import com.baha.url.preview.UrlInfoItem
import com.vitorpamplona.amethyst.R
@Composable @Composable
fun UrlPreviewCard( fun UrlPreviewCard(
@@ -36,7 +38,7 @@ fun UrlPreviewCard(
Column { Column {
AsyncImage( AsyncImage(
model = previewInfo.image, model = previewInfo.image,
contentDescription = "Preview Card Image for ${url}", contentDescription = stringResource(R.string.preview_card_image_for, url),
contentScale = ContentScale.FillWidth, contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@@ -17,7 +17,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R
@Composable @Composable
fun ZoomableAsyncImage(imageUrl: String) { fun ZoomableAsyncImage(imageUrl: String) {
@@ -46,9 +48,11 @@ fun ZoomableAsyncImage(imageUrl: String) {
) { ) {
AsyncImage( AsyncImage(
model = imageUrl, model = imageUrl,
contentDescription = "Profile Image", contentDescription = stringResource(id = R.string.profile_image),
contentScale = ContentScale.FillWidth, contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxSize().graphicsLayer( modifier = Modifier
.fillMaxSize()
.graphicsLayer(
scaleX = scale, scaleX = scale,
scaleY = scale, scaleY = scale,
translationX = offsetX, translationX = offsetX,
@@ -36,6 +36,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
@@ -201,7 +202,7 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel)
placeholder = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), placeholder = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)),
fallback = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), fallback = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)),
error = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), error = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)),
contentDescription = "Profile Image", contentDescription = stringResource(id = R.string.profile_image),
modifier = Modifier modifier = Modifier
.width(34.dp) .width(34.dp)
.height(34.dp) .height(34.dp)
@@ -51,6 +51,8 @@ import com.vitorpamplona.amethyst.ui.components.ResizeImage
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.platform.LocalContext
@Composable @Composable
fun DrawerContent(navController: NavHostController, fun DrawerContent(navController: NavHostController,
@@ -111,7 +113,7 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol
if (banner != null && banner.isNotBlank()) { if (banner != null && banner.isNotBlank()) {
AsyncImageProxy( AsyncImageProxy(
model = ResizeImage(banner, 150.dp), model = ResizeImage(banner, 150.dp),
contentDescription = "Profile Image", contentDescription = stringResource(id = R.string.profile_image),
contentScale = ContentScale.FillWidth, contentScale = ContentScale.FillWidth,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -120,7 +122,7 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol
} else { } else {
Image( Image(
painter = painterResource(R.drawable.profile_banner), painter = painterResource(R.drawable.profile_banner),
contentDescription = "Profile Banner", contentDescription = stringResource(R.string.profile_banner),
contentScale = ContentScale.FillWidth, contentScale = ContentScale.FillWidth,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -131,7 +133,7 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol
Column(modifier = modifier) { Column(modifier = modifier) {
AsyncImageProxy( AsyncImageProxy(
model = ResizeImage(accountUser.profilePicture(), 100.dp), model = ResizeImage(accountUser.profilePicture(), 100.dp),
contentDescription = "Profile Image", contentDescription = stringResource(id = R.string.profile_image),
placeholder = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), placeholder = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)),
fallback = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), fallback = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)),
error = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), error = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)),
@@ -187,11 +189,11 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol
})) { })) {
Row() { Row() {
Text("${accountUserFollows.follows.size}", fontWeight = FontWeight.Bold) Text("${accountUserFollows.follows.size}", fontWeight = FontWeight.Bold)
Text(" Following") Text(stringResource(R.string.following))
} }
Row(modifier = Modifier.padding(start = 10.dp)) { Row(modifier = Modifier.padding(start = 10.dp)) {
Text("${accountUserFollows.followers.size}", fontWeight = FontWeight.Bold) Text("${accountUserFollows.followers.size}", fontWeight = FontWeight.Bold)
Text(" Followers") Text(stringResource(R.string.followers))
} }
} }
} }
@@ -216,7 +218,7 @@ fun ListContent(
scaffoldState, scaffoldState,
"User/${accountUser.pubkeyHex}", "User/${accountUser.pubkeyHex}",
Route.Profile.icon, Route.Profile.icon,
"Profile" stringResource(R.string.profile)
) )
Divider( Divider(
@@ -231,14 +233,14 @@ fun ListContent(
} }
})) { })) {
Text( Text(
text = "Security Filters", text = stringResource(R.string.security_filters),
fontSize = 18.sp, fontSize = 18.sp,
fontWeight = W500 fontWeight = W500
) )
} }
Row(modifier = Modifier.clickable(onClick = { accountViewModel.logOff() })) { Row(modifier = Modifier.clickable(onClick = { accountViewModel.logOff() })) {
Text( Text(
text = "Log out", text = stringResource(R.string.log_out),
modifier = Modifier.padding(vertical = 15.dp), modifier = Modifier.padding(vertical = 15.dp),
fontSize = 18.sp, fontSize = 18.sp,
fontWeight = W500 fontWeight = W500
@@ -15,9 +15,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
@@ -37,7 +39,7 @@ fun BlankNote(modifier: Modifier = Modifier, isQuote: Boolean = false) {
horizontalArrangement = Arrangement.Center horizontalArrangement = Arrangement.Center
) { ) {
Text( Text(
text = "Post not found", text = stringResource(R.string.referenced_post_not_found),
modifier = Modifier.padding(30.dp), modifier = Modifier.padding(30.dp),
color = Color.Gray, color = Color.Gray,
) )
@@ -67,7 +69,7 @@ fun HiddenNote(reports: Set<Note>, loggedIn: User, modifier: Modifier = Modifier
) { ) {
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(30.dp)) { Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(30.dp)) {
Text( Text(
text = "Post was flagged as inappropriate by", text = stringResource(R.string.post_was_flagged_as_inappropriate_by),
color = Color.Gray, color = Color.Gray,
) )
FlowRow(modifier = Modifier.padding(top = 10.dp)) { FlowRow(modifier = Modifier.padding(top = 10.dp)) {
@@ -91,7 +93,7 @@ fun HiddenNote(reports: Set<Note>, loggedIn: User, modifier: Modifier = Modifier
), ),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp) contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
) { ) {
Text(text = "Show Anyway", color = Color.White) Text(text = stringResource(R.string.show_anyway), color = Color.White)
} }
} }
} }
@@ -30,6 +30,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.text.style.TextDirection
@@ -38,6 +39,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.RoboHashCache import com.vitorpamplona.amethyst.RoboHashCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
@@ -71,9 +73,9 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
val noteEvent = note.event val noteEvent = note.event
val description = if (noteEvent is ChannelCreateEvent) { val description = if (noteEvent is ChannelCreateEvent) {
"Channel created" stringResource(R.string.channel_created)
} else if (noteEvent is ChannelMetadataEvent) { } else if (noteEvent is ChannelMetadataEvent) {
"Channel Information changed to " "${stringResource(R.string.channel_information_changed_to)} "
} else { } else {
noteEvent?.content noteEvent?.content
} }
@@ -97,7 +99,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content) style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
) )
Text( Text(
" Public Chat", " ${stringResource(R.string.public_chat)}",
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
}, },
@@ -158,7 +160,7 @@ fun ChannelName(
placeholder = channelPicturePlaceholder, placeholder = channelPicturePlaceholder,
fallback = channelPicturePlaceholder, fallback = channelPicturePlaceholder,
error = channelPicturePlaceholder, error = channelPicturePlaceholder,
contentDescription = "Channel Image", contentDescription = stringResource(R.string.channel_image),
modifier = Modifier modifier = Modifier
.width(55.dp) .width(55.dp)
.height(55.dp) .height(55.dp)
@@ -182,6 +184,8 @@ fun ChannelName(
hasNewMessages: Boolean, hasNewMessages: Boolean,
onClick: () -> Unit onClick: () -> Unit
) { ) {
val context = LocalContext.current
Column(modifier = Modifier.clickable(onClick = onClick) ) { Column(modifier = Modifier.clickable(onClick = onClick) ) {
Row( Row(
modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp) modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp)
@@ -198,7 +202,7 @@ fun ChannelName(
channelLastTime?.let { channelLastTime?.let {
Text( Text(
timeAgo(channelLastTime), timeAgo(channelLastTime, context),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.52f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.52f)
) )
} }
@@ -217,7 +221,7 @@ fun ChannelName(
) )
else else
Text( Text(
"Referenced event not found", stringResource(R.string.referenced_event_not_found),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.52f), color = MaterialTheme.colors.onSurface.copy(alpha = 0.52f),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
@@ -45,6 +45,7 @@ import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -53,6 +54,7 @@ import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.RoboHashCache import com.vitorpamplona.amethyst.RoboHashCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
@@ -193,7 +195,7 @@ fun ChatroomMessageCompose(
placeholder = BitmapPainter(RoboHashCache.get(context, author.pubkeyHex)), placeholder = BitmapPainter(RoboHashCache.get(context, author.pubkeyHex)),
fallback = BitmapPainter(RoboHashCache.get(context, author.pubkeyHex)), fallback = BitmapPainter(RoboHashCache.get(context, author.pubkeyHex)),
error = BitmapPainter(RoboHashCache.get(context, author.pubkeyHex)), error = BitmapPainter(RoboHashCache.get(context, author.pubkeyHex)),
contentDescription = "Profile Image", contentDescription = stringResource(id = R.string.profile_image),
modifier = Modifier modifier = Modifier
.width(25.dp) .width(25.dp)
.height(25.dp) .height(25.dp)
@@ -238,15 +240,19 @@ fun ChatroomMessageCompose(
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
val event = note.event val event = note.event
if (event is ChannelCreateEvent) { if (event is ChannelCreateEvent) {
Text(text = "${note.author?.toBestDisplayName()} created " + Text(text = note.author?.toBestDisplayName()
"${event.channelInfo.name ?: ""} with " + .toString() + " ${stringResource(R.string.created)} " + (event.channelInfo.name
"description of '${event.channelInfo.about ?: ""}', " + ?: "") +" ${stringResource(R.string.with_description_of)} '" + (event.channelInfo.about
"and picture '${event.channelInfo.picture ?: ""}'") ?: "") + "', ${stringResource(R.string.and_picture)} '" + (event.channelInfo.picture
?: "") + "'"
)
} else if (event is ChannelMetadataEvent) { } else if (event is ChannelMetadataEvent) {
Text(text = "${note.author?.toBestDisplayName()} changed " + Text(text = note.author?.toBestDisplayName()
"chat name to '${event.channelInfo.name ?: ""}', " + .toString() + " ${stringResource(R.string.changed_chat_name_to)} '" + (event.channelInfo.name
"description to '${event.channelInfo.about ?: ""}', " + ?: "") + "$', {stringResource(R.string.description_to)} '" + (event.channelInfo.about
"and picture to '${event.channelInfo.picture ?: ""}'") ?: "") + "', ${stringResource(R.string.and_picture_to)} '" + (event.channelInfo.picture
?: "") + "'"
)
} else { } else {
val eventContent = accountViewModel.decrypt(note) val eventContent = accountViewModel.decrypt(note)
@@ -266,7 +272,7 @@ fun ChatroomMessageCompose(
) )
} else { } else {
TranslateableRichTextViewer( TranslateableRichTextViewer(
"Could Not decrypt the message", stringResource(R.string.could_not_decrypt_the_message),
true, true,
Modifier, Modifier,
note.event?.tags, note.event?.tags,
@@ -289,7 +295,7 @@ fun ChatroomMessageCompose(
) { ) {
Row() { Row() {
Text( Text(
timeAgoShort(note.event?.createdAt), timeAgoShort(note.event?.createdAt, context),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
fontSize = 12.sp fontSize = 12.sp
) )
@@ -343,7 +349,7 @@ private fun RelayBadges(baseNote: Note) {
placeholder = BitmapPainter(RoboHashCache.get(ctx, url)), placeholder = BitmapPainter(RoboHashCache.get(ctx, url)),
fallback = BitmapPainter(RoboHashCache.get(ctx, url)), fallback = BitmapPainter(RoboHashCache.get(ctx, url)),
error = BitmapPainter(RoboHashCache.get(ctx, url)), error = BitmapPainter(RoboHashCache.get(ctx, url)),
contentDescription = "Relay Icon", contentDescription = stringResource(id = R.string.relay_icon),
colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }), colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }),
modifier = Modifier modifier = Modifier
.fillMaxSize(1f) .fillMaxSize(1f)
@@ -22,6 +22,7 @@ import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
@@ -185,7 +186,7 @@ fun NoteCompose(
placeholder = BitmapPainter(RoboHashCache.get(context, channel.idHex)), placeholder = BitmapPainter(RoboHashCache.get(context, channel.idHex)),
fallback = BitmapPainter(RoboHashCache.get(context, channel.idHex)), fallback = BitmapPainter(RoboHashCache.get(context, channel.idHex)),
error = BitmapPainter(RoboHashCache.get(context, channel.idHex)), error = BitmapPainter(RoboHashCache.get(context, channel.idHex)),
contentDescription = "Group Picture", contentDescription = stringResource(R.string.group_picture),
modifier = Modifier modifier = Modifier
.width(30.dp) .width(30.dp)
.height(30.dp) .height(30.dp)
@@ -234,7 +235,7 @@ fun NoteCompose(
} }
Text( Text(
timeAgo(noteEvent.createdAt), timeAgo(noteEvent.createdAt, context = context),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
maxLines = 1 maxLines = 1
) )
@@ -317,11 +318,11 @@ fun NoteCompose(
} else if (noteEvent is ReportEvent) { } else if (noteEvent is ReportEvent) {
val reportType = noteEvent.reportType.map { val reportType = noteEvent.reportType.map {
when (it) { when (it) {
ReportEvent.ReportType.EXPLICIT -> "Explicit Content" ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content)
ReportEvent.ReportType.SPAM -> "Spam" ReportEvent.ReportType.SPAM -> stringResource(R.string.spam)
ReportEvent.ReportType.IMPERSONATION -> "Impersonation" ReportEvent.ReportType.IMPERSONATION -> stringResource(R.string.impersonation)
ReportEvent.ReportType.ILLEGAL -> "Illegal Behavior" ReportEvent.ReportType.ILLEGAL -> stringResource(R.string.illegal_behavior)
else -> "Unknown" else -> stringResource(R.string.unknown)
} }
}.joinToString(", ") }.joinToString(", ")
@@ -391,7 +392,7 @@ private fun RelayBadges(baseNote: Note) {
placeholder = BitmapPainter(RoboHashCache.get(ctx, url)), placeholder = BitmapPainter(RoboHashCache.get(ctx, url)),
fallback = BitmapPainter(RoboHashCache.get(ctx, url)), fallback = BitmapPainter(RoboHashCache.get(ctx, url)),
error = BitmapPainter(RoboHashCache.get(ctx, url)), error = BitmapPainter(RoboHashCache.get(ctx, url)),
contentDescription = "Relay Icon", contentDescription = stringResource(R.string.relay_icon),
colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }), colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }),
modifier = Modifier modifier = Modifier
.fillMaxSize(1f) .fillMaxSize(1f)
@@ -460,7 +461,7 @@ fun NoteAuthorPicture(
if (author == null) { if (author == null) {
Image( Image(
painter = BitmapPainter(RoboHashCache.get(ctx, "ohnothisauthorisnotfound")), painter = BitmapPainter(RoboHashCache.get(ctx, "ohnothisauthorisnotfound")),
contentDescription = "Unknown Author", contentDescription = stringResource(R.string.unknown_author),
modifier = pictureModifier modifier = pictureModifier
.fillMaxSize(1f) .fillMaxSize(1f)
.clip(shape = CircleShape) .clip(shape = CircleShape)
@@ -505,7 +506,7 @@ fun UserPicture(
AsyncImageProxy( AsyncImageProxy(
model = ResizeImage(user.profilePicture(), size), model = ResizeImage(user.profilePicture(), size),
contentDescription = "Profile Image", contentDescription = stringResource(id = R.string.profile_image),
placeholder = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)), placeholder = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)),
fallback = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)), fallback = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)),
error = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)), error = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)),
@@ -544,7 +545,7 @@ fun UserPicture(
Icon( Icon(
painter = painterResource(R.drawable.ic_verified), painter = painterResource(R.drawable.ic_verified),
"Following", stringResource(id = R.string.following),
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
tint = Following tint = Following
) )
@@ -564,17 +565,17 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
onDismissRequest = onDismiss onDismissRequest = onDismiss
) { ) {
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(accountViewModel.decrypt(note) ?: "")); onDismiss() }) { DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(accountViewModel.decrypt(note) ?: "")); onDismiss() }) {
Text("Copy Text") Text(stringResource(R.string.copy_text))
} }
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.author?.pubkeyNpub() ?: "")); onDismiss() }) { DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.author?.pubkeyNpub() ?: "")); onDismiss() }) {
Text("Copy User PubKey") Text(stringResource(R.string.copy_user_pubkey))
} }
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.idNote())); onDismiss() }) { DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.idNote())); onDismiss() }) {
Text("Copy Note ID") Text(stringResource(R.string.copy_note_id))
} }
Divider() Divider()
DropdownMenuItem(onClick = { accountViewModel.broadcast(note); onDismiss() }) { DropdownMenuItem(onClick = { accountViewModel.broadcast(note); onDismiss() }) {
Text("Broadcast") Text(stringResource(R.string.broadcast))
} }
if (note.author == accountViewModel.accountLiveData.value?.account?.userProfile()) { if (note.author == accountViewModel.accountLiveData.value?.account?.userProfile()) {
Divider() Divider()
@@ -592,7 +593,7 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
) )
}; onDismiss() }; onDismiss()
}) { }) {
Text("Block & Hide Author") Text(stringResource(R.string.block_hide_user))
} }
Divider() Divider()
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
@@ -600,28 +601,28 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
note.author?.let { accountViewModel.hide(it, context) } note.author?.let { accountViewModel.hide(it, context) }
onDismiss() onDismiss()
}) { }) {
Text("Report Spam / Scam") Text(stringResource(R.string.report_spam_scam))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.IMPERSONATION); accountViewModel.report(note, ReportEvent.ReportType.IMPERSONATION);
note.author?.let { accountViewModel.hide(it, context) } note.author?.let { accountViewModel.hide(it, context) }
onDismiss() onDismiss()
}) { }) {
Text("Report Impersonation") Text(stringResource(R.string.report_impersonation))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.EXPLICIT); accountViewModel.report(note, ReportEvent.ReportType.EXPLICIT);
note.author?.let { accountViewModel.hide(it, context) } note.author?.let { accountViewModel.hide(it, context) }
onDismiss() onDismiss()
}) { }) {
Text("Report Explicit Content") Text(stringResource(R.string.report_explicit_content))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.ILLEGAL); accountViewModel.report(note, ReportEvent.ReportType.ILLEGAL);
note.author?.let { accountViewModel.hide(it, context) } note.author?.let { accountViewModel.hide(it, context) }
onDismiss() onDismiss()
}) { }) {
Text("Report Illegal Behaviour") Text(stringResource(R.string.report_illegal_behaviour))
} }
} }
} }
@@ -47,6 +47,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
@@ -141,7 +142,7 @@ fun ReplyReaction(
scope.launch { scope.launch {
Toast.makeText( Toast.makeText(
context, context,
"Login with a Private key to be able to reply", context.getString(R.string.login_with_a_private_key_to_be_able_to_reply),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} }
@@ -193,7 +194,7 @@ private fun BoostReaction(
scope.launch { scope.launch {
Toast.makeText( Toast.makeText(
context, context,
"Login with a Private key to be able to boost posts", context.getString(R.string.login_with_a_private_key_to_be_able_to_boost_posts),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} }
@@ -264,7 +265,7 @@ fun LikeReaction(
scope.launch { scope.launch {
Toast.makeText( Toast.makeText(
context, context,
"Login with a Private key to like Posts", context.getString(R.string.login_with_a_private_key_to_like_posts),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} }
@@ -329,7 +330,7 @@ fun ZapReaction(
Toast Toast
.makeText( .makeText(
context, context,
"No Zap Amount Setup. Long Press to change", context.getString(R.string.no_zap_amount_setup_long_press_to_change),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
) )
.show() .show()
@@ -339,7 +340,7 @@ fun ZapReaction(
Toast Toast
.makeText( .makeText(
context, context,
"Login with a Private key to be able to send Zaps", context.getString(R.string.login_with_a_private_key_to_be_able_to_send_zaps),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
) )
.show() .show()
@@ -386,14 +387,14 @@ fun ZapReaction(
if (zappedNote?.isZappedBy(account.userProfile()) == true) { if (zappedNote?.isZappedBy(account.userProfile()) == true) {
Icon( Icon(
imageVector = Icons.Default.Bolt, imageVector = Icons.Default.Bolt,
contentDescription = "Zaps", contentDescription = stringResource(R.string.zaps),
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
tint = BitcoinOrange tint = BitcoinOrange
) )
} else { } else {
Icon( Icon(
imageVector = Icons.Outlined.Bolt, imageVector = Icons.Outlined.Bolt,
contentDescription = "Zaps", contentDescription = stringResource(id = R.string.zaps),
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
tint = grayTint tint = grayTint
) )
@@ -433,7 +434,7 @@ private fun ViewCountReaction(baseNote: Note, textModifier: Modifier = Modifier)
.diskCachePolicy(CachePolicy.DISABLED) .diskCachePolicy(CachePolicy.DISABLED)
.memoryCachePolicy(CachePolicy.ENABLED) .memoryCachePolicy(CachePolicy.ENABLED)
.build(), .build(),
contentDescription = "View count", contentDescription = stringResource(R.string.view_count),
modifier = Modifier.height(24.dp), modifier = Modifier.height(24.dp),
colorFilter = ColorFilter.tint(grayTint) colorFilter = ColorFilter.tint(grayTint)
) )
@@ -461,7 +462,7 @@ private fun BoostTypeChoicePopup(baseNote: Note, accountViewModel: AccountViewMo
backgroundColor = MaterialTheme.colors.primary backgroundColor = MaterialTheme.colors.primary
) )
) { ) {
Text("Boost", color = Color.White, textAlign = TextAlign.Center) Text(stringResource(R.string.boost), color = Color.White, textAlign = TextAlign.Center)
} }
Button( Button(
@@ -473,7 +474,7 @@ private fun BoostTypeChoicePopup(baseNote: Note, accountViewModel: AccountViewMo
backgroundColor = MaterialTheme.colors.primary backgroundColor = MaterialTheme.colors.primary
) )
) { ) {
Text("Quote", color = Color.White, textAlign = TextAlign.Center) Text(stringResource(R.string.quote), color = Color.White, textAlign = TextAlign.Center)
} }
} }
} }
@@ -640,7 +641,7 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
OutlinedTextField( OutlinedTextField(
label = { Text(text = "New Amount in Sats") }, label = { Text(text = stringResource(R.string.new_amount_in_sats)) },
value = postViewModel.nextAmount, value = postViewModel.nextAmount,
onValueChange = { onValueChange = {
postViewModel.nextAmount = it postViewModel.nextAmount = it
@@ -656,7 +657,9 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account) {
) )
}, },
singleLine = true, singleLine = true,
modifier = Modifier.padding(end = 10.dp).weight(1f) modifier = Modifier
.padding(end = 10.dp)
.weight(1f)
) )
Button( Button(
@@ -667,7 +670,7 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account) {
backgroundColor = MaterialTheme.colors.primary backgroundColor = MaterialTheme.colors.primary
) )
) { ) {
Text(text = "Add", color = Color.White) Text(text = stringResource(R.string.add), color = Color.White)
} }
} }
} }
@@ -17,10 +17,13 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.RelayInfo import com.vitorpamplona.amethyst.model.RelayInfo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import java.time.Instant import java.time.Instant
@@ -38,6 +41,8 @@ fun RelayCompose(
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
val context = LocalContext.current
Column() { Column() {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -58,13 +63,13 @@ fun RelayCompose(
) )
Text( Text(
timeAgo(relay.lastEvent), timeAgo(relay.lastEvent, context = context),
maxLines = 1 maxLines = 1
) )
} }
Text( Text(
"${relay.counter} posts received", "${relay.counter} ${stringResource(R.string.posts_received)}",
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@@ -99,7 +104,7 @@ fun AddRelayButton(onClick: () -> Unit) {
), ),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp) contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
) { ) {
Text(text = "Add", color = Color.White) Text(text = stringResource(id = R.string.add), color = Color.White)
} }
} }
@@ -115,7 +120,7 @@ fun RemoveRelayButton(onClick: () -> Unit) {
), ),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp) contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
) { ) {
Text(text = "Remove", color = Color.White) Text(text = stringResource(R.string.remove), color = Color.White)
} }
} }
@@ -7,6 +7,9 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -15,6 +18,7 @@ import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
@@ -37,7 +41,7 @@ fun ReplyInformation(replyTo: List<Note>?, dupMentions: List<User>?, account: Ac
val repliesToDisplay = if (expanded) mentions else mentions.take(2) val repliesToDisplay = if (expanded) mentions else mentions.take(2)
Text( Text(
"replying to ", stringResource(R.string.replying_to),
fontSize = 13.sp, fontSize = 13.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
@@ -62,7 +66,7 @@ fun ReplyInformation(replyTo: List<Note>?, dupMentions: List<User>?, account: Ac
) )
} else if (idx < repliesToDisplay.size - 1) { } else if (idx < repliesToDisplay.size - 1) {
Text( Text(
" and ", " ${stringResource(R.string.and)} ",
fontSize = 13.sp, fontSize = 13.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
@@ -76,7 +80,7 @@ fun ReplyInformation(replyTo: List<Note>?, dupMentions: List<User>?, account: Ac
) )
} else if (idx < repliesToDisplay.size) { } else if (idx < repliesToDisplay.size) {
Text( Text(
" and ", " ${stringResource(R.string.and)} ",
fontSize = 13.sp, fontSize = 13.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
@@ -128,7 +132,7 @@ fun ReplyInformationChannel(replyTo: List<Note>?,
FlowRow() { FlowRow() {
Text( Text(
"in channel ", stringResource(R.string.in_channel),
fontSize = 13.sp, fontSize = 13.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
@@ -142,7 +146,7 @@ fun ReplyInformationChannel(replyTo: List<Note>?,
if (mentions != null && mentions.isNotEmpty()) { if (mentions != null && mentions.isNotEmpty()) {
if (replyTo != null && replyTo.isNotEmpty()) { if (replyTo != null && replyTo.isNotEmpty()) {
Text( Text(
"replying to ", stringResource(id = R.string.replying_to),
fontSize = 13.sp, fontSize = 13.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
@@ -168,7 +172,7 @@ fun ReplyInformationChannel(replyTo: List<Note>?,
) )
} else if (idx < mentionSet.size - 1) { } else if (idx < mentionSet.size - 1) {
Text( Text(
" and ", " ${stringResource(id = R.string.add)} ",
fontSize = 13.sp, fontSize = 13.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
@@ -1,10 +1,12 @@
package com.vitorpamplona.amethyst.ui.note package com.vitorpamplona.amethyst.ui.note
import android.content.Context
import android.text.format.DateUtils import android.text.format.DateUtils
import com.vitorpamplona.amethyst.R
fun timeAgo(mills: Long?): String { fun timeAgo(mills: Long?, context : Context): String {
if (mills == null) return " " if (mills == null) return " "
if (mills == 0L) return " • never" if (mills == 0L) return "${context.getString(R.string.never)}"
var humanReadable = DateUtils.getRelativeTimeSpanString( var humanReadable = DateUtils.getRelativeTimeSpanString(
mills * 1000, mills * 1000,
@@ -13,13 +15,13 @@ fun timeAgo(mills: Long?): String {
DateUtils.FORMAT_ABBREV_ALL DateUtils.FORMAT_ABBREV_ALL
).toString() ).toString()
if (humanReadable.startsWith("In") || humanReadable.startsWith("0")) { if (humanReadable.startsWith("In") || humanReadable.startsWith("0")) {
humanReadable = "now"; humanReadable = context.getString(R.string.now);
} }
return "" + humanReadable return "" + humanReadable
.replace(" hr. ago", "h") .replace(" hr. ago", context.getString(R.string.h))
.replace(" min. ago", "m") .replace(" min. ago", context.getString(R.string.m))
.replace(" days ago", "d") .replace(" days ago", context.getString(R.string.d))
} }
fun timeAgoShort(mills: Long?): String { fun timeAgoShort(mills: Long?): String {
@@ -38,7 +40,7 @@ fun timeAgoShort(mills: Long?): String {
return humanReadable return humanReadable
} }
fun timeAgoLong(mills: Long?): String { fun timeAgoLong(mills: Long?, context: Context): String {
if (mills == null) return " " if (mills == null) return " "
var humanReadable = DateUtils.getRelativeTimeSpanString( var humanReadable = DateUtils.getRelativeTimeSpanString(
@@ -48,7 +50,7 @@ fun timeAgoLong(mills: Long?): String {
DateUtils.FORMAT_SHOW_TIME DateUtils.FORMAT_SHOW_TIME
).toString() ).toString()
if (humanReadable.startsWith("In") || humanReadable.startsWith("0")) { if (humanReadable.startsWith("In") || humanReadable.startsWith("0")) {
humanReadable = "now"; humanReadable = context.getString(R.string.now);
} }
return humanReadable return humanReadable
@@ -15,11 +15,14 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.LnZapEvent
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -94,7 +97,7 @@ fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewMode
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Text( Text(
showAmount(amount) + " sats", "${showAmount(amount)} ${stringResource(R.string.sats)}",
color = BitcoinOrange, color = BitcoinOrange,
fontSize = 20.sp, fontSize = 20.sp,
fontWeight = FontWeight.W500, fontWeight = FontWeight.W500,
@@ -24,10 +24,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.ui.screen.ZapSetCard import com.vitorpamplona.amethyst.ui.screen.ZapSetCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -94,9 +96,11 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, modifier: Modifier = Modifier, isInner
.padding(0.dp)) { .padding(0.dp)) {
Icon( Icon(
imageVector = Icons.Default.Bolt, imageVector = Icons.Default.Bolt,
contentDescription = "Zaps", contentDescription = stringResource(id = R.string.zaps),
tint = BitcoinOrange, tint = BitcoinOrange,
modifier = Modifier.size(25.dp).align(Alignment.TopEnd) modifier = Modifier
.size(25.dp)
.align(Alignment.TopEnd)
) )
} }
} }
@@ -28,11 +28,15 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import coil.compose.AsyncImage
import coil.compose.rememberAsyncImagePainter
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.RoboHashCache import com.vitorpamplona.amethyst.RoboHashCache
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.actions.CloseButton import com.vitorpamplona.amethyst.ui.actions.CloseButton
@@ -83,7 +87,7 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
placeholder = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)), placeholder = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)),
fallback = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)), fallback = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)),
error = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)), error = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)),
contentDescription = "Profile Image", contentDescription = stringResource(R.string.profile_image),
modifier = Modifier modifier = Modifier
.width(100.dp) .width(100.dp)
.height(100.dp) .height(100.dp)
@@ -129,7 +133,7 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
backgroundColor = MaterialTheme.colors.primary backgroundColor = MaterialTheme.colors.primary
) )
) { ) {
Text(text = "Scan QR") Text(text = stringResource(R.string.scan_qr))
} }
} }
@@ -137,7 +141,7 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) { Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) {
Text( Text(
"Point to the QR Code", stringResource(R.string.point_to_the_qr_code),
modifier = Modifier.padding(top = 7.dp), modifier = Modifier.padding(top = 7.dp),
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 25.sp fontSize = 25.sp
@@ -167,7 +171,7 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
backgroundColor = MaterialTheme.colors.primary backgroundColor = MaterialTheme.colors.primary
) )
) { ) {
Text(text = "Show QR") Text(text = stringResource(R.string.show_qr))
} }
} }
@@ -22,10 +22,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -122,7 +124,7 @@ fun LoadingFeed() {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
) { ) {
Text("Loading feed") Text(stringResource(R.string.loading_feed))
} }
} }
@@ -135,12 +137,12 @@ fun FeedError(errorMessage: String, onRefresh: () -> Unit) {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
) { ) {
Text("Error loading replies: $errorMessage") Text("${stringResource(R.string.error_loading_replies)} $errorMessage")
Button( Button(
modifier = Modifier.align(Alignment.CenterHorizontally), modifier = Modifier.align(Alignment.CenterHorizontally),
onClick = onRefresh onClick = onRefresh
) { ) {
Text(text = "Try again") Text(text = stringResource(R.string.try_again))
} }
} }
} }
@@ -154,9 +156,9 @@ fun FeedEmpty(onRefresh: () -> Unit) {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
) { ) {
Text("Feed is empty.") Text(stringResource(R.string.feed_is_empty))
OutlinedButton(onClick = onRefresh) { OutlinedButton(onClick = onRefresh) {
Text(text = "Refresh") Text(text = stringResource(R.string.refresh))
} }
} }
} }
@@ -50,6 +50,7 @@ import com.vitorpamplona.amethyst.ui.note.ReactionsRow
import com.vitorpamplona.amethyst.ui.note.timeAgo import com.vitorpamplona.amethyst.ui.note.timeAgo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import androidx.compose.ui.platform.LocalContext
@Composable @Composable
fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController) { fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController) {
@@ -192,6 +193,8 @@ fun NoteMaster(baseNote: Note,
var showHiddenNote by remember { mutableStateOf(false) } var showHiddenNote by remember { mutableStateOf(false) }
val context = LocalContext.current
var moreActionsExpanded by remember { mutableStateOf(false) } var moreActionsExpanded by remember { mutableStateOf(false) }
val noteEvent = note?.event val noteEvent = note?.event
@@ -232,7 +235,7 @@ fun NoteMaster(baseNote: Note,
NoteUsernameDisplay(baseNote, Modifier.weight(1f)) NoteUsernameDisplay(baseNote, Modifier.weight(1f))
Text( Text(
timeAgo(noteEvent.createdAt), timeAgo(noteEvent.createdAt, context = context),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
maxLines = 1 maxLines = 1
) )
@@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.map import androidx.lifecycle.map
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AccountState import com.vitorpamplona.amethyst.model.AccountState
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
@@ -51,7 +52,7 @@ class AccountViewModel(private val account: Account): ViewModel() {
val lud16 = note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim() val lud16 = note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim()
if (lud16.isNullOrBlank()) { if (lud16.isNullOrBlank()) {
onError("User does not have a lightning address setup to receive sats") onError(context.getString(R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats))
return return
} }
@@ -47,6 +47,7 @@ import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
@@ -59,6 +60,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.RoboHashCache import com.vitorpamplona.amethyst.RoboHashCache
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.Channel
@@ -183,7 +185,7 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
modifier = Modifier.weight(1f, true), modifier = Modifier.weight(1f, true),
placeholder = { placeholder = {
Text( Text(
text = "reply here.. ", text = stringResource(R.string.reply_here),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
}, },
@@ -226,7 +228,7 @@ fun ChannelHeader(baseChannel: Channel, account: Account, accountStateViewModel:
placeholder = BitmapPainter(RoboHashCache.get(context, channel.idHex)), placeholder = BitmapPainter(RoboHashCache.get(context, channel.idHex)),
fallback = BitmapPainter(RoboHashCache.get(context, channel.idHex)), fallback = BitmapPainter(RoboHashCache.get(context, channel.idHex)),
error = BitmapPainter(RoboHashCache.get(context, channel.idHex)), error = BitmapPainter(RoboHashCache.get(context, channel.idHex)),
contentDescription = "Profile Image", contentDescription = context.getString(R.string.profile_image),
modifier = Modifier modifier = Modifier
.width(35.dp) .width(35.dp)
.height(35.dp) .height(35.dp)
@@ -302,7 +304,7 @@ private fun NoteCopyButton(
Icon( Icon(
tint = Color.White, tint = Color.White,
imageVector = Icons.Default.Share, imageVector = Icons.Default.Share,
contentDescription = "Copies the Note ID to the clipboard for sharing" contentDescription = stringResource(R.string.copies_the_note_id_to_the_clipboard_for_sharing)
) )
DropdownMenu( DropdownMenu(
@@ -310,7 +312,7 @@ private fun NoteCopyButton(
onDismissRequest = { popupExpanded = false } onDismissRequest = { popupExpanded = false }
) { ) {
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.idNote())); popupExpanded = false }) { DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.idNote())); popupExpanded = false }) {
Text("Copy Channel ID (Note) to the Clipboard") Text(stringResource(R.string.copy_channel_id_note_to_the_clipboard))
} }
} }
} }
@@ -339,7 +341,7 @@ private fun EditButton(account: Account, channel: Channel) {
Icon( Icon(
tint = Color.White, tint = Color.White,
imageVector = Icons.Default.EditNote, imageVector = Icons.Default.EditNote,
contentDescription = "Edits the Channel Metadata" contentDescription = stringResource(R.string.edits_the_channel_metadata)
) )
} }
} }
@@ -359,7 +361,7 @@ private fun JoinButton(account: Account, channel: Channel, navController: NavCon
), ),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp) contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
) { ) {
Text(text = "Join", color = Color.White) Text(text = stringResource(R.string.join), color = Color.White)
} }
} }
@@ -378,6 +380,6 @@ private fun LeaveButton(account: Account, channel: Channel, navController: NavCo
), ),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp) contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
) { ) {
Text(text = "Leave", color = Color.White) Text(text = stringResource(R.string.leave), color = Color.White)
} }
} }
@@ -16,6 +16,7 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
@@ -32,6 +33,7 @@ import com.vitorpamplona.amethyst.ui.screen.ChatroomListFeedView
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.vitorpamplona.amethyst.R
@OptIn(ExperimentalPagerApi::class) @OptIn(ExperimentalPagerApi::class)
@Composable @Composable
@@ -57,7 +59,7 @@ fun ChatroomListScreen(accountViewModel: AccountViewModel, navController: NavCon
selected = pagerState.currentPage == 0, selected = pagerState.currentPage == 0,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } }, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
text = { text = {
Text(text = "Known") Text(text = stringResource(R.string.known))
} }
) )
@@ -65,7 +67,7 @@ fun ChatroomListScreen(accountViewModel: AccountViewModel, navController: NavCon
selected = pagerState.currentPage == 1, selected = pagerState.currentPage == 1,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } }, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
text = { text = {
Text(text = "New Requests") Text(text = stringResource(R.string.new_requests))
} }
) )
} }
@@ -39,6 +39,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.text.style.TextDirection
@@ -47,6 +48,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.RoboHashCache import com.vitorpamplona.amethyst.RoboHashCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
@@ -169,7 +171,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
shape = RoundedCornerShape(25.dp), shape = RoundedCornerShape(25.dp),
placeholder = { placeholder = {
Text( Text(
text = "reply here.. ", text = stringResource(id = R.string.reply_here),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
}, },
@@ -216,7 +218,7 @@ fun ChatroomHeader(baseUser: User, accountViewModel: AccountViewModel, navContro
placeholder = BitmapPainter(RoboHashCache.get(ctx, author.pubkeyHex)), placeholder = BitmapPainter(RoboHashCache.get(ctx, author.pubkeyHex)),
fallback = BitmapPainter(RoboHashCache.get(ctx, author.pubkeyHex)), fallback = BitmapPainter(RoboHashCache.get(ctx, author.pubkeyHex)),
error = BitmapPainter(RoboHashCache.get(ctx, author.pubkeyHex)), error = BitmapPainter(RoboHashCache.get(ctx, author.pubkeyHex)),
contentDescription = "Profile Image", contentDescription = stringResource(id = R.string.profile_image),
modifier = Modifier modifier = Modifier
.width(35.dp) .width(35.dp)
.height(35.dp) .height(35.dp)
@@ -13,6 +13,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
@@ -20,6 +21,7 @@ import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.pagerTabIndicatorOffset import com.google.accompanist.pager.pagerTabIndicatorOffset
import com.google.accompanist.pager.rememberPagerState import com.google.accompanist.pager.rememberPagerState
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.dal.HiddenAccountsFeedFilter import com.vitorpamplona.amethyst.ui.dal.HiddenAccountsFeedFilter
import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.UserFeedView import com.vitorpamplona.amethyst.ui.screen.UserFeedView
@@ -55,7 +57,7 @@ fun FiltersScreen(accountViewModel: AccountViewModel, navController: NavControll
selected = pagerState.currentPage == 0, selected = pagerState.currentPage == 0,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } }, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
text = { text = {
Text(text = "Blocked Users") Text(text = stringResource(R.string.blocked_users))
} }
) )
} }
@@ -16,6 +16,7 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
@@ -25,6 +26,7 @@ import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.pagerTabIndicatorOffset import com.google.accompanist.pager.pagerTabIndicatorOffset
import com.google.accompanist.pager.rememberPagerState import com.google.accompanist.pager.rememberPagerState
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.NostrHomeDataSource import com.vitorpamplona.amethyst.service.NostrHomeDataSource
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
@@ -95,7 +97,7 @@ fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController)
selected = pagerState.currentPage == 0, selected = pagerState.currentPage == 0,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } }, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
text = { text = {
Text(text = "New Threads") Text(text = stringResource(R.string.new_threads))
} }
) )
@@ -103,7 +105,7 @@ fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController)
selected = pagerState.currentPage == 1, selected = pagerState.currentPage == 1,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } }, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
text = { text = {
Text(text = "Conversations") Text(text = stringResource(R.string.conversations))
} }
) )
} }
@@ -31,6 +31,7 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@@ -179,34 +180,34 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
) { ) {
val tabs = listOf<@Composable() (() -> Unit)?>( val tabs = listOf<@Composable() (() -> Unit)?>(
{ {
Text(text = "Notes") Text(text = stringResource(R.string.notes))
}, },
{ {
Text(text = "Replies") Text(text = stringResource(R.string.replies))
}, },
{ {
val userState by baseUser.live().follows.observeAsState() val userState by baseUser.live().follows.observeAsState()
val userFollows = userState?.user?.follows?.size ?: "--" val userFollows = userState?.user?.follows?.size ?: "--"
Text(text = "$userFollows Follows") Text(text = "$userFollows ${stringResource(R.string.follows)}")
}, },
{ {
val userState by baseUser.live().follows.observeAsState() val userState by baseUser.live().follows.observeAsState()
val userFollows = userState?.user?.followers?.size ?: "--" val userFollowers = userState?.user?.followers?.size ?: "--"
Text(text = "$userFollows Followers") Text(text = "$userFollowers ${stringResource(id = R.string.followers)}")
}, },
{ {
val userState by baseUser.live().zaps.observeAsState() val userState by baseUser.live().zaps.observeAsState()
val userZaps = userState?.user?.zappedAmount() val userZaps = userState?.user?.zappedAmount()
Text(text = "${showAmount(userZaps)} Zaps") Text(text = "${showAmount(userZaps)} ${stringResource(id = R.string.zaps)}")
}, },
{ {
val userState by baseUser.live().reports.observeAsState() val userState by baseUser.live().reports.observeAsState()
val userReports = userState?.user?.reports?.values?.flatten()?.count() val userReports = userState?.user?.reports?.values?.flatten()?.count()
Text(text = "${userReports} Reports") Text(text = "${userReports.toString()} ${stringResource(R.string.reports)}")
}, },
{ {
val userState by baseUser.live().relays.observeAsState() val userState by baseUser.live().relays.observeAsState()
@@ -215,7 +216,7 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
val userStateRelayInfo by baseUser.live().relayInfo.observeAsState() val userStateRelayInfo by baseUser.live().relayInfo.observeAsState()
val userRelays = userStateRelayInfo?.user?.relays?.size ?: "--" val userRelays = userStateRelayInfo?.user?.relays?.size ?: "--"
Text(text = "$userRelaysBeingUsed / $userRelays Relays") Text(text = "$userRelaysBeingUsed / $userRelays ${stringResource(R.string.relays)}")
} }
) )
@@ -288,7 +289,7 @@ private fun ProfileHeader(
Icon( Icon(
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
imageVector = Icons.Default.MoreVert, imageVector = Icons.Default.MoreVert,
contentDescription = "More Options", contentDescription = stringResource(R.string.more_options),
) )
UserProfileDropDownMenu(baseUser, popupExpanded, { popupExpanded = false }, accountViewModel) UserProfileDropDownMenu(baseUser, popupExpanded, { popupExpanded = false }, accountViewModel)
@@ -386,7 +387,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account) {
Icon( Icon(
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
imageVector = Icons.Default.Link, imageVector = Icons.Default.Link,
contentDescription = "Website", contentDescription = stringResource(R.string.website),
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
) )
@@ -408,7 +409,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account) {
Icon( Icon(
tint = BitcoinOrange, tint = BitcoinOrange,
imageVector = Icons.Default.Bolt, imageVector = Icons.Default.Bolt,
contentDescription = "Lightning Address", contentDescription = stringResource(R.string.lightning_address),
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
) )
@@ -450,7 +451,7 @@ private fun DrawBanner(baseUser: User) {
if (banner != null && banner.isNotBlank()) { if (banner != null && banner.isNotBlank()) {
AsyncImageProxy( AsyncImageProxy(
model = ResizeImage(banner, 125.dp), model = ResizeImage(banner, 125.dp),
contentDescription = "Profile Image", contentDescription = stringResource(id = R.string.profile_image),
contentScale = ContentScale.FillWidth, contentScale = ContentScale.FillWidth,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -459,7 +460,7 @@ private fun DrawBanner(baseUser: User) {
} else { } else {
Image( Image(
painter = painterResource(R.drawable.profile_banner), painter = painterResource(R.drawable.profile_banner),
contentDescription = "Profile Banner", contentDescription = stringResource(id = R.string.profile_banner),
contentScale = ContentScale.FillWidth, contentScale = ContentScale.FillWidth,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -638,7 +639,7 @@ private fun NSecCopyButton(
Icon( Icon(
tint = Color.White, tint = Color.White,
imageVector = Icons.Default.Key, imageVector = Icons.Default.Key,
contentDescription = "Copies the Nsec ID (your password) to the clipboard for backup" contentDescription = stringResource(R.string.copies_the_nsec_id_your_password_to_the_clipboard_for_backup)
) )
DropdownMenu( DropdownMenu(
@@ -646,7 +647,7 @@ private fun NSecCopyButton(
onDismissRequest = { popupExpanded = false } onDismissRequest = { popupExpanded = false }
) { ) {
DropdownMenuItem(onClick = { account.loggedIn.privKey?.let { clipboardManager.setText(AnnotatedString(it.toNsec())) }; popupExpanded = false }) { DropdownMenuItem(onClick = { account.loggedIn.privKey?.let { clipboardManager.setText(AnnotatedString(it.toNsec())) }; popupExpanded = false }) {
Text("Copy Private Key to the Clipboard") Text(stringResource(R.string.copy_private_key_to_the_clipboard))
} }
} }
} }
@@ -673,7 +674,7 @@ private fun NPubCopyButton(
Icon( Icon(
tint = Color.White, tint = Color.White,
imageVector = Icons.Default.Share, imageVector = Icons.Default.Share,
contentDescription = "Copies the public key to the clipboard for sharing" contentDescription = stringResource(R.string.copies_the_public_key_to_the_clipboard_for_sharing)
) )
DropdownMenu( DropdownMenu(
@@ -681,7 +682,7 @@ private fun NPubCopyButton(
onDismissRequest = { popupExpanded = false } onDismissRequest = { popupExpanded = false }
) { ) {
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(user.pubkeyNpub())); popupExpanded = false }) { DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(user.pubkeyNpub())); popupExpanded = false }) {
Text("Copy Public Key (NPub) to the Clipboard") Text(stringResource(R.string.copy_public_key_npub_to_the_clipboard))
} }
} }
} }
@@ -702,7 +703,7 @@ private fun MessageButton(user: User, navController: NavController) {
) { ) {
Icon( Icon(
painter = painterResource(R.drawable.ic_dm), painter = painterResource(R.drawable.ic_dm),
"Send a Direct Message", stringResource(R.string.send_a_direct_message),
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
tint = Color.White tint = Color.White
) )
@@ -732,7 +733,7 @@ private fun EditButton(account: Account) {
Icon( Icon(
tint = Color.White, tint = Color.White,
imageVector = Icons.Default.EditNote, imageVector = Icons.Default.EditNote,
contentDescription = "Edits the User's Metadata" contentDescription = stringResource(R.string.edits_the_user_s_metadata)
) )
} }
} }
@@ -749,7 +750,7 @@ fun UnfollowButton(onClick: () -> Unit) {
), ),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp) contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
) { ) {
Text(text = "Unfollow", color = Color.White) Text(text = stringResource(R.string.unfollow), color = Color.White)
} }
} }
@@ -765,7 +766,7 @@ fun FollowButton(onClick: () -> Unit) {
), ),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp) contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
) { ) {
Text(text = "Follow", color = Color.White, textAlign = TextAlign.Center) Text(text = stringResource(R.string.follow), color = Color.White, textAlign = TextAlign.Center)
} }
} }
@@ -781,7 +782,7 @@ fun ShowUserButton(onClick: () -> Unit) {
), ),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp) contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
) { ) {
Text(text = "Unblock", color = Color.White) Text(text = stringResource(R.string.unblock), color = Color.White)
} }
} }
@@ -799,7 +800,7 @@ fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () ->
onDismissRequest = onDismiss onDismissRequest = onDismiss
) { ) {
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(user.pubkeyNpub())); onDismiss() }) { DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(user.pubkeyNpub())); onDismiss() }) {
Text("Copy User ID") Text(stringResource(R.string.copy_user_id))
} }
if ( account.userProfile() != user) { if ( account.userProfile() != user) {
@@ -813,11 +814,11 @@ fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () ->
) )
}; onDismiss() }; onDismiss()
}) { }) {
Text("Unblock User") Text(stringResource(R.string.unblock_user))
} }
} else { } else {
DropdownMenuItem(onClick = { user.let { accountViewModel.hide(it, context) }; onDismiss() }) { DropdownMenuItem(onClick = { user.let { accountViewModel.hide(it, context) }; onDismiss() }) {
Text("Block & Hide User") Text(stringResource(id = R.string.block_hide_user))
} }
} }
Divider() Divider()
@@ -826,28 +827,28 @@ fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () ->
user.let { accountViewModel.hide(it, context) } user.let { accountViewModel.hide(it, context) }
onDismiss() onDismiss()
}) { }) {
Text("Report Spam / Scam") Text(stringResource(id = R.string.report_spam_scam))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(user, ReportEvent.ReportType.IMPERSONATION); accountViewModel.report(user, ReportEvent.ReportType.IMPERSONATION);
user.let { accountViewModel.hide(it, context) } user.let { accountViewModel.hide(it, context) }
onDismiss() onDismiss()
}) { }) {
Text("Report Impersonation") Text(stringResource(id = R.string.report_impersonation))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(user, ReportEvent.ReportType.EXPLICIT); accountViewModel.report(user, ReportEvent.ReportType.EXPLICIT);
user.let { accountViewModel.hide(it, context) } user.let { accountViewModel.hide(it, context) }
onDismiss() onDismiss()
}) { }) {
Text("Report Explicit Content") Text(stringResource(id = R.string.report_explicit_content))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(user, ReportEvent.ReportType.ILLEGAL); accountViewModel.report(user, ReportEvent.ReportType.ILLEGAL);
user.let { accountViewModel.hide(it, context) } user.let { accountViewModel.hide(it, context) }
onDismiss() onDismiss()
}) { }) {
Text("Report Illegal Behaviour") Text(stringResource(id = R.string.report_illegal_behaviour))
} }
} }
} }
@@ -40,6 +40,7 @@ import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -203,7 +204,7 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
.defaultMinSize(minHeight = 20.dp), .defaultMinSize(minHeight = 20.dp),
placeholder = { placeholder = {
Text( Text(
text = "npub, hex, username ", text = stringResource(R.string.npub_hex_username),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
}, },
@@ -221,7 +222,7 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
) { ) {
Icon( Icon(
imageVector = Icons.Default.Clear, imageVector = Icons.Default.Clear,
contentDescription = "Clear" contentDescription = stringResource(R.string.clear)
) )
} }
} }
@@ -38,8 +38,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
@@ -60,6 +62,7 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
val acceptedTerms = remember { mutableStateOf(false) } val acceptedTerms = remember { mutableStateOf(false) }
var termsAcceptanceIsRequired by remember { mutableStateOf("") } var termsAcceptanceIsRequired by remember { mutableStateOf("") }
val uri = LocalUriHandler.current val uri = LocalUriHandler.current
val context = LocalContext.current
Column( Column(
modifier = Modifier modifier = Modifier
@@ -81,7 +84,7 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
Image( Image(
painterResource(id = R.drawable.amethyst), painterResource(id = R.drawable.amethyst),
contentDescription = "App Logo", contentDescription = stringResource(R.string.app_logo),
modifier = Modifier.size(200.dp), modifier = Modifier.size(200.dp),
contentScale = ContentScale.Inside contentScale = ContentScale.Inside
) )
@@ -102,7 +105,7 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
), ),
placeholder = { placeholder = {
Text( Text(
text = "nsec / npub / hex private key", text = stringResource(R.string.nsec_npub_hex_private_key),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
}, },
@@ -110,7 +113,8 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
IconButton(onClick = { showPassword = !showPassword }) { IconButton(onClick = { showPassword = !showPassword }) {
Icon( Icon(
imageVector = if (showPassword) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility, imageVector = if (showPassword) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility,
contentDescription = if (showPassword) "Show Password" else "Hide Password" contentDescription = if (showPassword) stringResource(R.string.show_password) else stringResource(
R.string.hide_password)
) )
} }
}, },
@@ -120,7 +124,7 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
try { try {
accountViewModel.login(key.value.text) accountViewModel.login(key.value.text)
} catch (e: Exception) { } catch (e: Exception) {
errorMessage = "Invalid key" errorMessage = context.getString(R.string.invalid_key)
} }
} }
) )
@@ -141,10 +145,10 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
onCheckedChange = { acceptedTerms.value = it } onCheckedChange = { acceptedTerms.value = it }
) )
Text(text = "I accept the ") Text(text = stringResource(R.string.i_accept_the))
ClickableText( ClickableText(
text = AnnotatedString("terms of use"), text = AnnotatedString(stringResource(R.string.terms_of_use)),
onClick = { runCatching { uri.openUri("https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md") } }, onClick = { runCatching { uri.openUri("https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md") } },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary), style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
) )
@@ -164,18 +168,18 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
Button( Button(
onClick = { onClick = {
if (!acceptedTerms.value) { if (!acceptedTerms.value) {
termsAcceptanceIsRequired = "Acceptance of terms is required" termsAcceptanceIsRequired = context.getString(R.string.acceptance_of_terms_is_required)
} }
if (key.value.text.isBlank()) { if (key.value.text.isBlank()) {
errorMessage = "Key is required" errorMessage = context.getString(R.string.key_is_required)
} }
if (acceptedTerms.value && key.value.text.isNotBlank()) { if (acceptedTerms.value && key.value.text.isNotBlank()) {
try { try {
accountViewModel.login(key.value.text) accountViewModel.login(key.value.text)
} catch (e: Exception) { } catch (e: Exception) {
errorMessage = "Invalid key" errorMessage = context.getString(R.string.invalid_key)
} }
} }
}, },
@@ -188,14 +192,14 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
backgroundColor = if (acceptedTerms.value) MaterialTheme.colors.primary else Color.Gray backgroundColor = if (acceptedTerms.value) MaterialTheme.colors.primary else Color.Gray
) )
) { ) {
Text(text = "Login") Text(text = stringResource(R.string.login))
} }
} }
} }
// The last child is glued to the bottom. // The last child is glued to the bottom.
ClickableText( ClickableText(
text = AnnotatedString("Generate a new key"), text = AnnotatedString(stringResource(R.string.generate_a_new_key)),
modifier = Modifier modifier = Modifier
.padding(20.dp) .padding(20.dp)
.fillMaxWidth(), .fillMaxWidth(),
@@ -203,7 +207,7 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
if (acceptedTerms.value) { if (acceptedTerms.value) {
accountViewModel.newKey() accountViewModel.newKey()
} else { } else {
termsAcceptanceIsRequired = "Acceptance of terms is required" termsAcceptanceIsRequired = context.getString(R.string.acceptance_of_terms_is_required)
} }
}, },
style = TextStyle( style = TextStyle(
+173
View File
@@ -0,0 +1,173 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name_release">Amethyst</string>
<string name="app_name_debug">Amethyst Debug</string>
<string name="point_to_the_qr_code">أشر إلى رمز QR</string>
<string name="show_qr">أظهر QR</string>
<string name="profile_image">صورة الملف الشخصي</string>
<string name="scan_qr">مسح QR</string>
<string name="show_anyway">عرض على أي حال</string>
<string name="post_was_flagged_as_inappropriate_by">تم وضع علامة على المنشور على أنه غير لائق بواسطة</string>
<string name="referenced_post_not_found">المنشور المشار إليه غير موجود</string>
<string name="channel_image">صورة القناة</string>
<string name="referenced_event_not_found">لم يتم العثور على الحدث المشار إليه</string>
<string name="could_not_decrypt_the_message">لا يمكن فك تشفير الرسالة</string>
<string name="group_picture">صورة المجموعة</string>
<string name="explicit_content">محتوى فاضح</string>
<string name="spam">زسائل عشوائية</string>
<string name="impersonation">التمثيل</string>
<string name="illegal_behavior">تصرف غير قانوني</string>
<string name="unknown">غير معروف</string>
<string name="relay_icon">Relay Icon</string>
<string name="unknown_author">كاتب غير معروف</string>
<string name="copy_text">نسخ النص</string>
<string name="copy_user_pubkey">نسخ المفتاح العام</string>
<string name="copy_note_id">نسخ معرف </string>
<string name="broadcast">بث</string>
<string name="block_hide_user">حظر و إخفاء المستخدم</string>
<string name="report_spam_scam">الابلاغ عن البريد العشوائي / الاحتيال</string>
<string name="report_impersonation">الإبلاغ عن التمثيل</string>
<string name="report_explicit_content">الإبلاغ عن المحتوى الصريح / الفاضح</string>
<string name="report_illegal_behaviour">الإبلاغ عن سلوك غير قانوني</string>
<string name="login_with_a_private_key_to_be_able_to_reply">قم بتسجيل الدخول باستخدام مفتاح خاص لتتمكن من الرد</string>
<string name="login_with_a_private_key_to_be_able_to_boost_posts">قم بتسجيل الدخول باستخدام مفتاح خاص لتتمكن من تعزيز المشاركات</string>
<string name="login_with_a_private_key_to_like_posts">تسجيل الدخول باستخدام مفتاح خاص لإبداء الإعجاب بالمنشورات</string>
<string name="no_zap_amount_setup_long_press_to_change">لا يوجد إعداد لمبلغ Zap. اضغط لفترة طويلة للتغيير</string>
<string name="login_with_a_private_key_to_be_able_to_send_zaps">قم بتسجيل الدخول باستخدام مفتاح خاص لتتمكن من إرسال Zaps</string>
<string name="zaps">Zaps</string>
<string name="view_count">مشاهدة العد</string>
<string name="boost">تعزيز</string>
<string name="quote">إقتباس</string>
<string name="new_amount_in_sats">مبلغ جديد في Sats</string>
<string name="add">إضافة</string>
<string name="replying_to">الرد على</string>
<string name="and">" و "</string>
<string name="in_channel">"في القناة "</string>
<string name="profile_banner">لافتة الملف الشخصي</string>
<string name="following">تابع</string>
<string name="followers">المتابعون</string>
<string name="profile">الملف الشخصي</string>
<string name="security_filters">مرشحات الأمان</string>
<string name="log_out">تسجيل الخروج</string>
<string name="show_more">إظهار المزيد</string>
<string name="lightning_invoice">Lightning فاتورة</string>
<string name="pay">اإدفع</string>
<string name="lightning_tips">Lightning تبرع</string>
<string name="note_to_receiver">ملاحظة للمستقبل</string>
<string name="thank_you_so_much">شكرا جزيلا!</string>
<string name="amount_in_sats">المبلغ في Sats</string>
<string name="send_sats">إرسال Sats</string>
<string name="never_translate_from">"لا تترجم ابداً من "</string>
<string name="error_parsing_preview_for">خطأ في تحليل المعاينة لـ %1$s : %2$s</string>
<string name="preview_card_image_for">معاينة صورة البطاقة لـ %1$s</string>
<string name="new_channel">قناة جديدة</string>
<string name="can_t_find_out_the_content_type">لا يمكن معرفة نوع المحتوى</string>
<string name="can_t_insert_the_new_content">لا يمكن إدراج المحتوى الجديد</string>
<string name="can_t_open_the_content_output_stream">لا يمكن فتح دفق إخراج المحتوى</string>
<string name="can_t_open_the_image_input_stream">لا يمكن فتح دفق إدخال الصورة</string>
<string name="there_must_be_an_uploaded_image_url_in_the_response">يجب أن يكون هناك عنوان URL للصورة التي تم تحميلها في الاستجابة</string>
<string name="channel_name">اسم القناة</string>
<string name="my_awesome_group">مجموعتي الرائعة</string>
<string name="picture_url">رابط الصورة</string>
<string name="description">الوصف</string>
<string name="about_us">معلومات عن القناة..</string>
<string name="what_s_on_your_mind">ما الذي يدور في ذهنك؟</string>
<string name="post">نشر</string>
<string name="save">حفظ</string>
<string name="create">إنشاء</string>
<string name="cancel">إلغاء</string>
<string name="failed_to_upload_the_image">فشل تحميل الصورة</string>
<string name="relay_address">Relay عنوان</string>
<string name="posts">المنشورات</string>
<string name="errors">الأخطاء</string>
<string name="home_feed">المنشورات الرئيسية</string>
<string name="private_message_feed"> الرسائل الخاصة</string>
<string name="public_chat_feed">الرسائل العامة</string>
<string name="global_feed">الموجز العام</string>
<string name="add_a_relay">أضف Relay</string>
<string name="display_name">إسم العرض</string>
<string name="my_display_name">إسم العرض الخاص بي</string>
<string name="username">اسم المستخدم</string>
<string name="my_username">اسم المستخدم الخاص بي</string>
<string name="about_me">ْعَنِّي</string>
<string name="avatar_url">رابط الصورة الزمزية</string>
<string name="banner_url">رابط الشعار</string>
<string name="website_url">رابط الموقع</string>
<string name="ln_address">LN عنوان</string>
<string name="ln_url_outdated">LN رابط (قديم)</string>
<string name="image_saved_to_the_gallery">تم حفظ الصورة في المعرض</string>
<string name="failed_to_save_the_image">فشل حفظ الصورة</string>
<string name="upload_image">تحميل الصورة</string>
<string name="uploading">جاري التحميل…</string>
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">لا يمتلك المستخدم إعداد عنوان lighting لاستقبال sats</string>
<string name="reply_here">"الرد هنا.. "</string>
<string name="copies_the_note_id_to_the_clipboard_for_sharing">نسخ معرف الملاحظة إلى الحافظة للمشاركة</string>
<string name="copy_channel_id_note_to_the_clipboard">انسخ معرف القناة (ملاحظة) إلى الحافظة</string>
<string name="edits_the_channel_metadata">تعديل البيانات الوصفية للقناة</string>
<string name="join">إنضمام</string>
<string name="known">معروف</string>
<string name="new_requests">طلبات جديدة</string>
<string name="blocked_users">المستخدمين المحظورين</string>
<string name="new_threads">سرد جديد</string>
<string name="conversations">المحادثات</string>
<string name="notes">الملاحظات</string>
<string name="replies">الردود</string>
<string name="follows">تابع</string>
<string name="reports">التقارير</string>
<string name="more_options">المزيد من الخيارات</string>
<string name="relays">Relays</string>
<string name="website">الموقع</string>
<string name="lightning_address">Lightning عنوان</string>
<string name="copies_the_nsec_id_your_password_to_the_clipboard_for_backup">نسخ معرف Nsec (كلمة المرور الخاصة بك) إلى الحافظة للنسخ الاحتياطي</string>
<string name="copy_private_key_to_the_clipboard">نسخ المفتاح الخاص إلى الحافظة</string>
<string name="copies_the_public_key_to_the_clipboard_for_sharing">نسخ المفتاح العام إلى الحافظة للمشاركة</string>
<string name="copy_public_key_npub_to_the_clipboard">انسخ المفتاح العام (NPub) إلى الحافظة</string>
<string name="send_a_direct_message">إرسال رسالة مباشرة</string>
<string name="edits_the_user_s_metadata">القيام بتحرير metadata للمستخدم</string>
<string name="follow">تابع</string>
<string name="unblock">رفع الحظر</string>
<string name="copy_user_id">نسخ معرف المستخدم</string>
<string name="unblock_user">إلغاء حظر عن مستخدم</string>
<string name="npub_hex_username">npub, hex, username </string>
<string name="clear">نظف</string>
<string name="app_logo">شعار التطبيق</string>
<string name="nsec_npub_hex_private_key">nsec / npub / hex private key</string>
<string name="show_password">إظهار كلمة السر</string>
<string name="hide_password">اخفاء كلمة السر</string>
<string name="invalid_key">مفتاح غير صحيح</string>
<string name="i_accept_the">"اقبل شروط الاستخدام "</string>
<string name="terms_of_use">شروط الاستخدام</string>
<string name="acceptance_of_terms_is_required">يجب قبول شروط الإستخدام</string>
<string name="key_is_required">المفتاح مطلوب</string>
<string name="login">تسجيل دخول</string>
<string name="generate_a_new_key">إنشاء مفتاح جديد</string>
<string name="loading_feed">بإنتظار التحديثات</string>
<string name="error_loading_replies">خطأ في تحميل الردود:</string>
<string name="try_again">المحاولة مرة اخرى</string>
<string name="feed_is_empty">لا توجد ملاحظات.</string>
<string name="refresh">تحديث</string>
<string name="created">انشئ</string>
<string name="with_description_of">مع وصف</string>
<string name="and_picture">و صورة</string>
<string name="changed_chat_name_to">تم تغيير اسم الدردشة إلى</string>
<string name="description_to">الوصف الى</string>
<string name="and_picture_to">و الصورة الى</string>
<string name="leave">مغادرة</string>
<string name="unfollow">الغاء المتابعة</string>
<string name="channel_created">تم انشاء القناة</string>
<string name="channel_information_changed_to">تم تغيير معلومات القناة إلى</string>
<string name="public_chat">الدردشة العامة</string>
<string name="posts_received">تم استلام المشاركات</string>
<string name="remove">ازالة</string>
<string name="auto">تلقائي</string>
<string name="translated_from">مترجم من</string>
<string name="to">الى</string>
<string name="show_in">تظهر في</string>
<string name="first">اولا</string>
<string name="always_translate_to">ترجمة إلى</string>
<string name="never">ابداً</string>
<string name="now">الان</string>
<string name="h">س</string>
<string name="m">د</string>
<string name="d">يوم</string>
</resources>
+173 -2
View File
@@ -1,4 +1,175 @@
<resources> <resources>
<string name="app_name_release">Amethyst</string> <string name="app_name_release" translatable="false">Amethyst</string>
<string name="app_name_debug">Amethyst Debug</string> <string name="app_name_debug" translatable="false">Amethyst Debug</string>
<string name="point_to_the_qr_code">Point to the QR Code</string>
<string name="show_qr">Show QR</string>
<string name="profile_image">Profile Image</string>
<string name="scan_qr">Scan QR</string>
<string name="show_anyway">Show Anyway</string>
<string name="post_was_flagged_as_inappropriate_by">Post was flagged as inappropriate by</string>
<string name="referenced_post_not_found">Referenced post not found</string>
<string name="channel_image">Channel Image</string>
<string name="referenced_event_not_found">Referenced event not found</string>
<string name="could_not_decrypt_the_message">Could Not decrypt the message</string>
<string name="group_picture">Group Picture</string>
<string name="explicit_content">Explicit Content</string>
<string name="spam">Spam</string>
<string name="impersonation">Impersonation</string>
<string name="illegal_behavior">Illegal Behavior</string>
<string name="unknown">Unknown</string>
<string name="relay_icon">Relay Icon</string>
<string name="unknown_author">Unknown Author</string>
<string name="copy_text">Copy Text</string>
<string name="copy_user_pubkey">Copy User PubKey</string>
<string name="copy_note_id">Copy Note ID</string>
<string name="broadcast">Broadcast</string>
<string name="block_hide_user"><![CDATA[Block & Hide User]]></string>
<string name="report_spam_scam">Report Spam / Scam</string>
<string name="report_impersonation">Report Impersonation</string>
<string name="report_explicit_content">Report Explicit Content</string>
<string name="report_illegal_behaviour">Report Illegal Behaviour</string>
<string name="login_with_a_private_key_to_be_able_to_reply">Login with a Private key to be able to reply</string>
<string name="login_with_a_private_key_to_be_able_to_boost_posts">Login with a Private key to be able to boost posts</string>
<string name="login_with_a_private_key_to_like_posts">Login with a Private key to like Posts</string>
<string name="no_zap_amount_setup_long_press_to_change">No Zap Amount Setup. Long Press to change</string>
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Login with a Private key to be able to send Zaps</string>
<string name="zaps">Zaps</string>
<string name="view_count">View count</string>
<string name="boost">Boost</string>
<string name="quote">Quote</string>
<string name="new_amount_in_sats">New Amount in Sats</string>
<string name="add">Add</string>
<string name="replying_to">"replying to "</string>
<string name="and">" and "</string>
<string name="in_channel">"in channel "</string>
<string name="profile_banner">Profile Banner</string>
<string name="following">"Following"</string>
<string name="followers">"Followers"</string>
<string name="profile">Profile</string>
<string name="security_filters">Security Filters</string>
<string name="log_out">Log out</string>
<string name="show_more">Show More</string>
<string name="lightning_invoice">Lightning Invoice</string>
<string name="pay">Pay</string>
<string name="lightning_tips">Lightning Tips</string>
<string name="note_to_receiver">Note to Receiver</string>
<string name="thank_you_so_much">Thank you so much!</string>
<string name="amount_in_sats">Amount in Sats</string>
<string name="send_sats">Send Sats</string>
<string name="never_translate_from">"Never translate from "</string>
<string name="error_parsing_preview_for">"Error parsing preview for %1$s : %2$s"</string>
<string name="preview_card_image_for">"Preview Card Image for %1$s"</string>
<string name="new_channel">New Channel</string>
<string name="can_t_find_out_the_content_type">Can\'t find out the content type</string>
<string name="can_t_insert_the_new_content">Can\'t insert the new content</string>
<string name="can_t_open_the_content_output_stream">Can\'t open the content output stream</string>
<string name="can_t_open_the_image_input_stream">Can\'t open the image input stream</string>
<string name="there_must_be_an_uploaded_image_url_in_the_response">There must be an uploaded image URL in the response</string>
<string name="channel_name">Channel Name</string>
<string name="my_awesome_group">My Awesome Group</string>
<string name="picture_url">Picture Url</string>
<string name="description">Description</string>
<string name="about_us">"About us.. "</string>
<string name="what_s_on_your_mind">What\'s on your mind?</string>
<string name="post">Post</string>
<string name="save">Save</string>
<string name="create">Create</string>
<string name="cancel">Cancel</string>
<string name="failed_to_upload_the_image">Failed to upload the image</string>
<string name="relay_address">Relay Address</string>
<string name="posts">Posts</string>
<string name="errors">Errors</string>
<string name="home_feed">Home Feed</string>
<string name="private_message_feed">Private Message Feed</string>
<string name="public_chat_feed">Public Chat Feed</string>
<string name="global_feed">Global Feed</string>
<string name="add_a_relay">Add a Relay</string>
<string name="display_name">Display Name</string>
<string name="my_display_name">My display name</string>
<string name="username">Username</string>
<string name="my_username">My username</string>
<string name="about_me">About me</string>
<string name="avatar_url">Avatar URL</string>
<string name="banner_url">Banner URL</string>
<string name="website_url">Website URL</string>
<string name="ln_address">LN Address</string>
<string name="ln_url_outdated">LN URL (outdated)</string>
<string name="image_saved_to_the_gallery">Image saved to the gallery</string>
<string name="failed_to_save_the_image">Failed to save the image</string>
<string name="upload_image">Upload Image</string>
<string name="uploading">Uploading…</string>
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">User does not have a lightning address setup to receive sats</string>
<string name="reply_here">"reply here.. "</string>
<string name="copies_the_note_id_to_the_clipboard_for_sharing">Copies the Note ID to the clipboard for sharing</string>
<string name="copy_channel_id_note_to_the_clipboard">Copy Channel ID (Note) to the Clipboard</string>
<string name="edits_the_channel_metadata">Edits the Channel Metadata</string>
<string name="join">Join</string>
<string name="known">Known</string>
<string name="new_requests">New Requests</string>
<string name="blocked_users">Blocked Users</string>
<string name="new_threads">New Threads</string>
<string name="conversations">Conversations</string>
<string name="notes">Notes</string>
<string name="replies">Replies</string>
<string name="follows">"Follows"</string>
<string name="reports">"Reports"</string>
<string name="more_options">More Options</string>
<string name="relays">" Relays"</string>
<string name="website">Website</string>
<string name="lightning_address">Lightning Address</string>
<string name="copies_the_nsec_id_your_password_to_the_clipboard_for_backup">Copies the Nsec ID (your password) to the clipboard for backup</string>
<string name="copy_private_key_to_the_clipboard">Copy Private Key to the Clipboard</string>
<string name="copies_the_public_key_to_the_clipboard_for_sharing">Copies the public key to the clipboard for sharing</string>
<string name="copy_public_key_npub_to_the_clipboard">Copy Public Key (NPub) to the Clipboard</string>
<string name="send_a_direct_message">Send a Direct Message</string>
<string name="edits_the_user_s_metadata">Edits the User\'s Metadata</string>
<string name="follow">Follow</string>
<string name="unblock">Unblock</string>
<string name="copy_user_id">Copy User ID</string>
<string name="unblock_user">Unblock User</string>
<string name="npub_hex_username">"npub, hex, username "</string>
<string name="clear">Clear</string>
<string name="app_logo">App Logo</string>
<string name="nsec_npub_hex_private_key">nsec / npub / hex private key</string>
<string name="show_password">Show Password</string>
<string name="hide_password">Hide Password</string>
<string name="invalid_key">Invalid key</string>
<string name="i_accept_the">"I accept the "</string>
<string name="terms_of_use">terms of use</string>
<string name="acceptance_of_terms_is_required">Acceptance of terms is required</string>
<string name="key_is_required">Key is required</string>
<string name="login">Login</string>
<string name="generate_a_new_key">Generate a new key</string>
<string name="loading_feed">Loading feed</string>
<string name="error_loading_replies">"Error loading replies: "</string>
<string name="try_again">Try again</string>
<string name="feed_is_empty">Feed is empty.</string>
<string name="refresh">Refresh</string>
<string name="created">created</string>
<string name="with_description_of">with description of</string>
<string name="and_picture">and picture</string>
<string name="changed_chat_name_to">changed chat name to</string>
<string name="description_to">description to</string>
<string name="and_picture_to">and picture to</string>
<string name="leave">Leave</string>
<string name="unfollow">Unfollow</string>
<string name="channel_created">Channel created</string>
<string name="channel_information_changed_to">"Channel Information changed to"</string>
<string name="public_chat">Public Chat</string>
<string name="posts_received">posts received</string>
<string name="remove">Remove</string>
<string name="sats" translatable="false">sats</string>
<string name="auto">Auto</string>
<string name="translated_from">translated from</string>
<string name="to">to</string>
<string name="show_in">Show in</string>
<string name="first">first</string>
<string name="always_translate_to">Always translate to</string>
<string name="nip_05" translatable="false">NIP-05</string>
<string name="lnurl" translatable="false">LNURL...</string>
<string name="never">never</string>
<string name="now">now</string>
<string name="h">h</string>
<string name="m">m</string>
<string name="d">d</string>
</resources> </resources>