Merge remote-tracking branch 'origin/HEAD'

# Conflicts:
#	app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreview.kt
This commit is contained in:
Vitor Pamplona 2023-03-01 10:02:41 -05:00
commit 01b8b2a5fa
50 changed files with 749 additions and 250 deletions

1
.idea/.name generated Normal file
View File

@ -0,0 +1 @@
Amethyst

2
.idea/compiler.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
<bytecodeTargetLevel target="17" />
</component>
</project>

View File

@ -5,6 +5,9 @@
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</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">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />

6
.idea/kotlinc.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.8.10" />
</component>
</project>

2
.idea/misc.xml generated
View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<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" />
</component>
<component name="ProjectType">

View File

@ -88,7 +88,7 @@ class Relay(
"NOTICE" -> listeners.forEach {
//Log.w("Relay", "Relay onNotice $url, $channel")
// "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 {
//Log.w("Relay", "Relay onOK $url, $channel")

View File

@ -21,6 +21,7 @@ import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.screen.AccountScreen
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
import java.util.Locale
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@ -61,6 +62,8 @@ class MainActivity : ComponentActivity() {
}
Client.lenient = true
Locale.setDefault(Locale.ENGLISH)
}
override fun onResume() {

View File

@ -10,6 +10,7 @@ import android.provider.MediaStore
import androidx.annotation.RequiresApi
import androidx.core.net.toUri
import java.io.File
import com.vitorpamplona.amethyst.R
import okhttp3.*
import okio.BufferedSource
import okio.IOException
@ -49,7 +50,7 @@ object ImageSaver {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentType = response.header("Content-Type")
checkNotNull(contentType) {
"Can't find out the content type"
context.getString(R.string.can_t_find_out_the_content_type)
}
saveContentQ(
@ -57,6 +58,7 @@ object ImageSaver {
contentType = contentType,
contentSource = response.body.source(),
contentResolver = context.contentResolver,
context = context
)
} else {
saveContentDefault(
@ -80,6 +82,7 @@ object ImageSaver {
contentType: String,
contentSource: BufferedSource,
contentResolver: ContentResolver,
context: Context
) {
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
@ -93,13 +96,13 @@ object ImageSaver {
val uri =
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
checkNotNull(uri) {
"Can't insert the new content"
context.getString(R.string.can_t_insert_the_new_content)
}
try {
val outputStream = contentResolver.openOutputStream(uri)
checkNotNull(outputStream) {
"Can't open the content output stream"
context.getString(R.string.can_t_open_the_content_output_stream)
}
outputStream.use {

View File

@ -1,6 +1,7 @@
package com.vitorpamplona.amethyst.ui.actions
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import java.io.IOException
@ -8,6 +9,7 @@ import java.util.UUID
import okhttp3.Call
import okhttp3.Callback
import okhttp3.MediaType
import com.vitorpamplona.amethyst.R
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
@ -23,6 +25,7 @@ object ImageUploader {
contentResolver: ContentResolver,
onSuccess: (String) -> Unit,
onError: (Throwable) -> Unit,
context : Context
) {
val contentType = contentResolver.getType(uri)
@ -40,7 +43,7 @@ object ImageUploader {
override fun writeTo(sink: BufferedSink) {
val imageInputStream = contentResolver.openInputStream(uri)
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)
@ -63,7 +66,7 @@ object ImageUploader {
val tree = jacksonObjectMapper().readTree(body.string())
val url = tree?.get("data")?.get("link")?.asText()
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)

View File

@ -17,12 +17,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.style.TextDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
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))
OutlinedTextField(
label = { Text(text = "Channel Name") },
label = { Text(text = stringResource(R.string.channel_name)) },
modifier = Modifier.fillMaxWidth(),
value = postViewModel.channelName.value,
onValueChange = { postViewModel.channelName.value = it },
placeholder = {
Text(
text = "My Awesome Group",
text = stringResource(R.string.my_awesome_group),
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))
OutlinedTextField(
label = { Text(text = "Picture Url") },
label = { Text(text = stringResource(R.string.picture_url)) },
modifier = Modifier.fillMaxWidth(),
value = postViewModel.channelPicture.value,
onValueChange = { postViewModel.channelPicture.value = it },
@ -101,13 +104,13 @@ fun NewChannelView(onClose: () -> Unit, account: Account, channel: Channel? = nu
Spacer(modifier = Modifier.height(15.dp))
OutlinedTextField(
label = { Text(text = "Description") },
label = { Text(text = stringResource(R.string.description)) },
modifier = Modifier.fillMaxWidth().height(100.dp),
value = postViewModel.channelDescription.value,
onValueChange = { postViewModel.channelDescription.value = it },
placeholder = {
Text(
text = "About us.. ",
text = stringResource(R.string.about_us),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},

View File

@ -25,6 +25,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.dp
@ -136,7 +137,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
},
placeholder = {
Text(
text = "What's on your mind?",
text = stringResource(R.string.what_s_on_your_mind),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},
@ -221,7 +222,7 @@ fun CloseButton(onCancel: () -> Unit) {
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
)
) {
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
)
) {
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
)
) {
Text(text = "Create", color = Color.White)
Text(text = stringResource(R.string.create), color = Color.White)
}
}

View File

@ -14,6 +14,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.parseDirtyWordForKey
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.components.isValidURL
import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator
import kotlinx.coroutines.flow.MutableSharedFlow
@ -133,9 +134,10 @@ class NewPostViewModel: ViewModel() {
onError = {
isUploadingImage = false
viewModelScope.launch {
imageUploadingError.emit("Failed to upload the image")
imageUploadingError.emit(context.getString(R.string.failed_to_upload_the_image))
}
}
},
context = context
)
}

View File

@ -41,6 +41,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@ -141,7 +142,7 @@ fun ServerConfigHeader() {
Column(Modifier.weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "Relay Address",
text = stringResource(R.string.relay_address),
modifier = Modifier.weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
@ -154,7 +155,7 @@ fun ServerConfigHeader() {
Spacer(modifier = Modifier.size(25.dp))
Text(
text = "Posts",
text = stringResource(R.string.posts),
maxLines = 1,
fontSize = 14.sp,
modifier = Modifier.weight(1f),
@ -164,7 +165,7 @@ fun ServerConfigHeader() {
Spacer(modifier = Modifier.size(10.dp))
Text(
text = "Posts",
text = stringResource(id = R.string.posts),
maxLines = 1,
fontSize = 14.sp,
modifier = Modifier.weight(1f),
@ -174,7 +175,7 @@ fun ServerConfigHeader() {
Spacer(modifier = Modifier.size(10.dp))
Text(
text = "Errors",
text = stringResource(R.string.errors),
maxLines = 1,
fontSize = 14.sp,
modifier = Modifier.weight(1f),
@ -227,7 +228,9 @@ fun ServerConfig(
Icon(
imageVector = Icons.Default.Cancel,
null,
modifier = Modifier.padding(end = 5.dp).size(15.dp),
modifier = Modifier
.padding(end = 5.dp)
.size(15.dp),
tint = Color.Red
)
}
@ -252,8 +255,10 @@ fun ServerConfig(
) {
Icon(
painterResource(R.drawable.ic_home),
"Home Feed",
modifier = Modifier.padding(end = 5.dp).size(15.dp),
stringResource(R.string.home_feed),
modifier = Modifier
.padding(end = 5.dp)
.size(15.dp),
tint = if (item.feedTypes.contains(FeedType.FOLLOWS)) Color.Green else MaterialTheme.colors.onSurface.copy(
alpha = 0.32f
)
@ -265,8 +270,10 @@ fun ServerConfig(
) {
Icon(
painterResource(R.drawable.ic_dm),
"Private Message Feed",
modifier = Modifier.padding(horizontal = 5.dp).size(15.dp),
stringResource(R.string.private_message_feed),
modifier = Modifier
.padding(horizontal = 5.dp)
.size(15.dp),
tint = if (item.feedTypes.contains(FeedType.PRIVATE_DMS)) Color.Green else MaterialTheme.colors.onSurface.copy(
alpha = 0.32f
)
@ -278,8 +285,10 @@ fun ServerConfig(
) {
Icon(
imageVector = Icons.Default.Groups,
"Public Chat Feed",
modifier = Modifier.padding(horizontal = 5.dp).size(15.dp),
stringResource(R.string.public_chat_feed),
modifier = Modifier
.padding(horizontal = 5.dp)
.size(15.dp),
tint = if (item.feedTypes.contains(FeedType.PUBLIC_CHATS)) Color.Green else MaterialTheme.colors.onSurface.copy(
alpha = 0.32f
)
@ -291,8 +300,10 @@ fun ServerConfig(
) {
Icon(
imageVector = Icons.Default.Public,
"Global Feed",
modifier = Modifier.padding(horizontal = 5.dp).size(15.dp),
stringResource(R.string.global_feed),
modifier = Modifier
.padding(horizontal = 5.dp)
.size(15.dp),
tint = if (item.feedTypes.contains(FeedType.GLOBAL)) Color.Green else MaterialTheme.colors.onSurface.copy(
alpha = 0.32f
)
@ -310,7 +321,9 @@ fun ServerConfig(
Icon(
imageVector = Icons.Default.Download,
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(
alpha = 0.32f
)
@ -332,7 +345,9 @@ fun ServerConfig(
Icon(
imageVector = Icons.Default.Upload,
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(
alpha = 0.32f
)
@ -350,7 +365,9 @@ fun ServerConfig(
Icon(
imageVector = Icons.Default.SyncProblem,
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
)
@ -396,7 +413,7 @@ fun EditableServerConfig(relayToAdd: String, onNewRelay: (RelaySetupInfo) -> Uni
Row(verticalAlignment = Alignment.CenterVertically) {
OutlinedTextField(
label = { Text(text = "Add a Relay") },
label = { Text(text = stringResource(R.string.add_a_relay)) },
modifier = Modifier.weight(1f),
value = url,
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)
)
) {
Text(text = "Add", color = Color.White)
Text(text = stringResource(id = R.string.add), color = Color.White)
}
}

View File

@ -17,11 +17,13 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
@OptIn(ExperimentalComposeUiApi::class)
@ -69,13 +71,13 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Row(modifier = Modifier.fillMaxWidth(1f), verticalAlignment = Alignment.CenterVertically) {
OutlinedTextField(
label = { Text(text = "Display Name") },
label = { Text(text = stringResource(R.string.display_name)) },
modifier = Modifier.weight(1f),
value = postViewModel.displayName.value,
onValueChange = { postViewModel.displayName.value = it },
placeholder = {
Text(
text = "My display name",
text = stringResource(R.string.my_display_name),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},
@ -88,13 +90,13 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Text("@", Modifier.padding(5.dp))
OutlinedTextField(
label = { Text(text = "Username") },
label = { Text(text = stringResource(R.string.username)) },
modifier = Modifier.weight(1f),
value = postViewModel.userName.value,
onValueChange = { postViewModel.userName.value = it },
placeholder = {
Text(
text = "My username",
text = stringResource(R.string.my_username),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},
@ -105,7 +107,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField(
label = { Text(text = "About me") },
label = { Text(text = stringResource(R.string.about_me)) },
modifier = Modifier
.fillMaxWidth()
.height(100.dp),
@ -113,7 +115,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
onValueChange = { postViewModel.about.value = it },
placeholder = {
Text(
text = "About me",
text = stringResource(id = R.string.about_me),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},
@ -126,7 +128,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField(
label = { Text(text = "Avatar URL") },
label = { Text(text = stringResource(R.string.avatar_url)) },
modifier = Modifier.fillMaxWidth(),
value = postViewModel.picture.value,
onValueChange = { postViewModel.picture.value = it },
@ -142,7 +144,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField(
label = { Text(text = "Banner URL") },
label = { Text(text = stringResource(R.string.banner_url)) },
modifier = Modifier.fillMaxWidth(),
value = postViewModel.banner.value,
onValueChange = { postViewModel.banner.value = it },
@ -158,7 +160,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField(
label = { Text(text = "Website URL") },
label = { Text(text = stringResource(R.string.website_url)) },
modifier = Modifier.fillMaxWidth(),
value = postViewModel.website.value,
onValueChange = { postViewModel.website.value = it },
@ -174,7 +176,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField(
label = { Text(text = "NIP-05") },
label = { Text(text = stringResource(R.string.nip_05)) },
modifier = Modifier.fillMaxWidth(),
value = postViewModel.nip05.value,
onValueChange = { postViewModel.nip05.value = it },
@ -189,7 +191,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField(
label = { Text(text = "LN Address") },
label = { Text(text = stringResource(R.string.ln_address)) },
modifier = Modifier.fillMaxWidth(),
value = postViewModel.lnAddress.value,
onValueChange = { postViewModel.lnAddress.value = it },
@ -205,13 +207,13 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField(
label = { Text(text = "LN URL (outdated)") },
label = { Text(text = stringResource(R.string.ln_url_outdated)) },
modifier = Modifier.fillMaxWidth(),
value = postViewModel.lnURL.value,
onValueChange = { postViewModel.lnURL.value = it },
placeholder = {
Text(
text = "LNURL...",
text = stringResource(R.string.lnurl),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},

View File

@ -11,10 +11,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.vitorpamplona.amethyst.R
import kotlinx.coroutines.launch
/**
@ -29,6 +31,7 @@ fun SaveToGallery(url: String) {
val localContext = LocalContext.current
val scope = rememberCoroutineScope()
fun saveImage() {
ImageSaver.saveImage(
context = localContext,
@ -37,7 +40,7 @@ fun SaveToGallery(url: String) {
scope.launch {
Toast.makeText(
localContext,
"Image saved to the gallery",
localContext.getString(R.string.image_saved_to_the_gallery),
Toast.LENGTH_SHORT
)
.show()
@ -47,7 +50,7 @@ fun SaveToGallery(url: String) {
scope.launch {
Toast.makeText(
localContext,
"Failed to save the image",
localContext.getString(R.string.failed_to_save_the_image),
Toast.LENGTH_SHORT
)
.show()
@ -78,6 +81,6 @@ fun SaveToGallery(url: String) {
backgroundColor = Color.Gray
)
) {
Text(text = "Save", color = Color.White)
Text(text = stringResource(id = R.string.save), color = Color.White)
}
}

View File

@ -12,10 +12,12 @@ import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.vitorpamplona.amethyst.R
@OptIn(ExperimentalPermissionsApi::class)
@Composable
@ -54,9 +56,9 @@ fun UploadFromGallery(
}
) {
if (!isUploading) {
Text("Upload Image")
Text(stringResource(R.string.upload_image))
} else {
Text("Uploading…")
Text(stringResource(R.string.uploading))
}
}
}
@ -68,9 +70,9 @@ fun UploadFromGallery(
enabled = !isUploading,
) {
if (!isUploading) {
Text("Upload Image")
Text(stringResource(R.string.upload_image))
} else {
Text("Uploading…")
Text(stringResource(R.string.uploading))
}
}
}

View File

@ -16,7 +16,9 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
@ -38,7 +40,7 @@ fun NewChannelButton(account: Account) {
) {
Icon(
imageVector = Icons.Outlined.Add,
contentDescription = "New Channel",
contentDescription = stringResource(R.string.new_channel),
modifier = Modifier.size(26.dp),
tint = Color.White
)

View File

@ -25,6 +25,8 @@ import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.R
import androidx.compose.ui.res.stringResource
@Composable
fun ExpandableRichTextViewer(
@ -67,7 +69,7 @@ fun ExpandableRichTextViewer(
),
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)
}
}
}

View File

@ -22,6 +22,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@ -68,7 +69,7 @@ fun InvoicePreview(lnInvoice: String) {
)
Text(
text = "Lightning Invoice",
text = stringResource(R.string.lightning_invoice),
fontSize = 20.sp,
fontWeight = FontWeight.W500,
modifier = Modifier.padding(start = 10.dp)
@ -79,7 +80,9 @@ fun InvoicePreview(lnInvoice: String) {
amount?.let {
Text(
text = "${NumberFormat.getInstance().format(amount)} sats",
text = "${
NumberFormat.getInstance().format(amount)
} ${stringResource(id = R.string.sats)}",
fontSize = 25.sp,
fontWeight = FontWeight.W500,
modifier = Modifier
@ -90,7 +93,9 @@ fun InvoicePreview(lnInvoice: String) {
Button(
modifier = Modifier.fillMaxWidth().padding(vertical = 10.dp),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 10.dp),
onClick = {
runCatching {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("lightning:$lnInvoice"))
@ -102,7 +107,7 @@ fun InvoicePreview(lnInvoice: String) {
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)
}
}
}

View File

@ -30,6 +30,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
@ -73,7 +74,7 @@ fun InvoiceRequest(lud16: String, toUserPubKeyHex: String, account: Account, onC
)
Text(
text = "Lightning Tips",
text = stringResource(R.string.lightning_tips),
fontSize = 20.sp,
fontWeight = FontWeight.W500,
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) }
OutlinedTextField(
label = { Text(text = "Note to Receiver") },
label = { Text(text = stringResource(R.string.note_to_receiver)) },
modifier = Modifier.fillMaxWidth(),
value = message,
onValueChange = { message = it },
placeholder = {
Text(
text = "Thank you so much!",
text = stringResource(R.string.thank_you_so_much),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},
@ -103,7 +104,7 @@ fun InvoiceRequest(lud16: String, toUserPubKeyHex: String, account: Account, onC
)
OutlinedTextField(
label = { Text(text = "Amount in Sats") },
label = { Text(text = stringResource(R.string.amount_in_sats)) },
modifier = Modifier.fillMaxWidth(),
value = amount.toString(),
onValueChange = {
@ -152,7 +153,7 @@ fun InvoiceRequest(lud16: String, toUserPubKeyHex: String, account: Account, onC
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)
}
}
}

View File

@ -27,6 +27,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
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.core.os.ConfigurationCompat
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService
import com.vitorpamplona.amethyst.service.lang.ResultOrError
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@ -110,17 +112,17 @@ fun TranslateableRichTextViewer(
val annotatedTranslationString = buildAnnotatedString {
withStyle(clickableTextStyle) {
pushStringAnnotation("langSettings", true.toString())
append("Auto")
append(stringResource(R.string.auto))
}
append("-translated from ")
append("-${stringResource(R.string.translated_from)} ")
withStyle(clickableTextStyle) {
pushStringAnnotation("showOriginal", true.toString())
append(Locale(source).displayName)
}
append(" to ")
append(" ${stringResource(R.string.to)} ")
withStyle(clickableTextStyle) {
pushStringAnnotation("showOriginal", false.toString())
@ -163,7 +165,7 @@ fun TranslateableRichTextViewer(
Spacer(modifier = Modifier.size(10.dp))
Text("Never translate from ${Locale(source).displayName}")
Text(stringResource(R.string.never_translate_from) + "${Locale(source).displayName}")
}
Divider()
DropdownMenuItem(onClick = {
@ -181,7 +183,14 @@ fun TranslateableRichTextViewer(
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 = {
accountViewModel.prefer(source, target, target)
@ -198,7 +207,7 @@ fun TranslateableRichTextViewer(
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()
@ -221,7 +230,7 @@ fun TranslateableRichTextViewer(
Spacer(modifier = Modifier.size(10.dp))
Text("Always translate to ${lang.displayName}")
Text("${stringResource(R.string.always_translate_to)}${lang.displayName}")
}
}
}

View File

@ -8,8 +8,10 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.baha.url.preview.IUrlPreviewCallback
import com.baha.url.preview.UrlInfoItem
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -18,6 +20,7 @@ import kotlinx.coroutines.withContext
@Composable
fun UrlPreview(url: String, urlText: String) {
val default = UrlCachedPreviewer.cache[url]?.let { UrlPreviewState.Loaded(it) } ?: UrlPreviewState.Loading
var context = LocalContext.current
var urlPreviewState by remember { mutableStateOf(default) }
@ -34,8 +37,13 @@ fun UrlPreview(url: String, urlText: String) {
}
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
)
)
}
})
}

View File

@ -15,10 +15,12 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.baha.url.preview.UrlInfoItem
import com.vitorpamplona.amethyst.R
@Composable
fun UrlPreviewCard(
@ -36,7 +38,7 @@ fun UrlPreviewCard(
Column {
AsyncImage(
model = previewInfo.image,
contentDescription = "Preview Card Image for ${url}",
contentDescription = stringResource(R.string.preview_card_image_for, url),
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth()
)

View File

@ -17,7 +17,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R
@Composable
fun ZoomableAsyncImage(imageUrl: String) {
@ -46,14 +48,16 @@ fun ZoomableAsyncImage(imageUrl: String) {
) {
AsyncImage(
model = imageUrl,
contentDescription = "Profile Image",
contentDescription = stringResource(id = R.string.profile_image),
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxSize().graphicsLayer(
scaleX = scale,
scaleY = scale,
translationX = offsetX,
translationY = offsetY
),
modifier = Modifier
.fillMaxSize()
.graphicsLayer(
scaleX = scale,
scaleY = scale,
translationX = offsetX,
translationY = offsetY
),
)
}
}

View File

@ -36,6 +36,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
@ -201,7 +202,7 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel)
placeholder = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)),
fallback = 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
.width(34.dp)
.height(34.dp)

View File

@ -51,6 +51,8 @@ import com.vitorpamplona.amethyst.ui.components.ResizeImage
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.launch
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.platform.LocalContext
@Composable
fun DrawerContent(navController: NavHostController,
@ -111,7 +113,7 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol
if (banner != null && banner.isNotBlank()) {
AsyncImageProxy(
model = ResizeImage(banner, 150.dp),
contentDescription = "Profile Image",
contentDescription = stringResource(id = R.string.profile_image),
contentScale = ContentScale.FillWidth,
modifier = Modifier
.fillMaxWidth()
@ -120,7 +122,7 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol
} else {
Image(
painter = painterResource(R.drawable.profile_banner),
contentDescription = "Profile Banner",
contentDescription = stringResource(R.string.profile_banner),
contentScale = ContentScale.FillWidth,
modifier = Modifier
.fillMaxWidth()
@ -131,7 +133,7 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol
Column(modifier = modifier) {
AsyncImageProxy(
model = ResizeImage(accountUser.profilePicture(), 100.dp),
contentDescription = "Profile Image",
contentDescription = stringResource(id = R.string.profile_image),
placeholder = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)),
fallback = 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() {
Text("${accountUserFollows.follows.size}", fontWeight = FontWeight.Bold)
Text(" Following")
Text(stringResource(R.string.following))
}
Row(modifier = Modifier.padding(start = 10.dp)) {
Text("${accountUserFollows.followers.size}", fontWeight = FontWeight.Bold)
Text(" Followers")
Text(stringResource(R.string.followers))
}
}
}
@ -216,7 +218,7 @@ fun ListContent(
scaffoldState,
"User/${accountUser.pubkeyHex}",
Route.Profile.icon,
"Profile"
stringResource(R.string.profile)
)
Divider(
@ -231,14 +233,14 @@ fun ListContent(
}
})) {
Text(
text = "Security Filters",
text = stringResource(R.string.security_filters),
fontSize = 18.sp,
fontWeight = W500
)
}
Row(modifier = Modifier.clickable(onClick = { accountViewModel.logOff() })) {
Text(
text = "Log out",
text = stringResource(R.string.log_out),
modifier = Modifier.padding(vertical = 15.dp),
fontSize = 18.sp,
fontWeight = W500

View File

@ -15,9 +15,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
@ -37,7 +39,7 @@ fun BlankNote(modifier: Modifier = Modifier, isQuote: Boolean = false) {
horizontalArrangement = Arrangement.Center
) {
Text(
text = "Post not found",
text = stringResource(R.string.post_not_found),
modifier = Modifier.padding(30.dp),
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)) {
Text(
text = "Post was flagged as inappropriate by",
text = stringResource(R.string.post_was_flagged_as_inappropriate_by),
color = Color.Gray,
)
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)
) {
Text(text = "Show Anyway", color = Color.White)
Text(text = stringResource(R.string.show_anyway), color = Color.White)
}
}
}

View File

@ -30,6 +30,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
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.navigation.NavController
import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.RoboHashCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
@ -71,9 +73,9 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
val noteEvent = note.event
val description = if (noteEvent is ChannelCreateEvent) {
"Channel created"
stringResource(R.string.channel_created)
} else if (noteEvent is ChannelMetadataEvent) {
"Channel Information changed to "
"${stringResource(R.string.channel_information_changed_to)} "
} else {
noteEvent?.content
}
@ -97,7 +99,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
)
Text(
" Public Chat",
" ${stringResource(R.string.public_chat)}",
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},
@ -158,7 +160,7 @@ fun ChannelName(
placeholder = channelPicturePlaceholder,
fallback = channelPicturePlaceholder,
error = channelPicturePlaceholder,
contentDescription = "Channel Image",
contentDescription = stringResource(R.string.channel_image),
modifier = Modifier
.width(55.dp)
.height(55.dp)
@ -182,6 +184,8 @@ fun ChannelName(
hasNewMessages: Boolean,
onClick: () -> Unit
) {
val context = LocalContext.current
Column(modifier = Modifier.clickable(onClick = onClick) ) {
Row(
modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp)
@ -198,7 +202,7 @@ fun ChannelName(
channelLastTime?.let {
Text(
timeAgo(channelLastTime),
timeAgo(channelLastTime, context),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.52f)
)
}
@ -217,7 +221,7 @@ fun ChannelName(
)
else
Text(
"Referenced event not found",
stringResource(R.string.referenced_event_not_found),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.52f),
maxLines = 1,
overflow = TextOverflow.Ellipsis,

View File

@ -45,6 +45,7 @@ import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
@ -53,6 +54,7 @@ import androidx.navigation.NavController
import coil.compose.AsyncImage
import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.RoboHashCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
@ -193,7 +195,7 @@ fun ChatroomMessageCompose(
placeholder = BitmapPainter(RoboHashCache.get(context, author.pubkeyHex)),
fallback = 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
.width(25.dp)
.height(25.dp)
@ -238,15 +240,19 @@ fun ChatroomMessageCompose(
Row(verticalAlignment = Alignment.CenterVertically) {
val event = note.event
if (event is ChannelCreateEvent) {
Text(text = "${note.author?.toBestDisplayName()} created " +
"${event.channelInfo.name ?: ""} with " +
"description of '${event.channelInfo.about ?: ""}', " +
"and picture '${event.channelInfo.picture ?: ""}'")
Text(text = note.author?.toBestDisplayName()
.toString() + " ${stringResource(R.string.created)} " + (event.channelInfo.name
?: "") +" ${stringResource(R.string.with_description_of)} '" + (event.channelInfo.about
?: "") + "', ${stringResource(R.string.and_picture)} '" + (event.channelInfo.picture
?: "") + "'"
)
} else if (event is ChannelMetadataEvent) {
Text(text = "${note.author?.toBestDisplayName()} changed " +
"chat name to '${event.channelInfo.name ?: ""}', " +
"description to '${event.channelInfo.about ?: ""}', " +
"and picture to '${event.channelInfo.picture ?: ""}'")
Text(text = note.author?.toBestDisplayName()
.toString() + " ${stringResource(R.string.changed_chat_name_to)} '" + (event.channelInfo.name
?: "") + "$', {stringResource(R.string.description_to)} '" + (event.channelInfo.about
?: "") + "', ${stringResource(R.string.and_picture_to)} '" + (event.channelInfo.picture
?: "") + "'"
)
} else {
val eventContent = accountViewModel.decrypt(note)
@ -266,7 +272,7 @@ fun ChatroomMessageCompose(
)
} else {
TranslateableRichTextViewer(
"Could Not decrypt the message",
stringResource(R.string.could_not_decrypt_the_message),
true,
Modifier,
note.event?.tags,
@ -289,7 +295,7 @@ fun ChatroomMessageCompose(
) {
Row() {
Text(
timeAgoShort(note.event?.createdAt),
timeAgoShort(note.event?.createdAt, context),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
fontSize = 12.sp
)
@ -343,7 +349,7 @@ private fun RelayBadges(baseNote: Note) {
placeholder = BitmapPainter(RoboHashCache.get(ctx, url)),
fallback = 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) }),
modifier = Modifier
.fillMaxSize(1f)

View File

@ -22,6 +22,7 @@ import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
@ -186,7 +187,7 @@ fun NoteCompose(
placeholder = BitmapPainter(RoboHashCache.get(context, channel.idHex)),
fallback = BitmapPainter(RoboHashCache.get(context, channel.idHex)),
error = BitmapPainter(RoboHashCache.get(context, channel.idHex)),
contentDescription = "Group Picture",
contentDescription = stringResource(R.string.group_picture),
modifier = Modifier
.width(30.dp)
.height(30.dp)
@ -235,7 +236,7 @@ fun NoteCompose(
}
Text(
timeAgo(noteEvent.createdAt),
timeAgo(noteEvent.createdAt, context = context),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
maxLines = 1
)
@ -258,7 +259,7 @@ fun NoteCompose(
if (note.author != null && !makeItShort) {
ObserveDisplayNip05Status(note.author!!)
}
Spacer(modifier = Modifier.height(3.dp))
if (noteEvent is TextNoteEvent && (note.replyTo != null || note.mentions != null)) {
@ -316,15 +317,15 @@ fun NoteCompose(
)
}
} else if (noteEvent is ReportEvent) {
val reportType = (noteEvent.reportedPost + noteEvent.reportedAuthor).map {
when (it.reportType) {
ReportEvent.ReportType.EXPLICIT -> "Explicit Content"
ReportEvent.ReportType.NUDITY -> "Nudity"
ReportEvent.ReportType.PROFANITY -> "Profanity / Hateful speech"
ReportEvent.ReportType.SPAM -> "Spam"
ReportEvent.ReportType.IMPERSONATION -> "Impersonation"
ReportEvent.ReportType.ILLEGAL -> "Illegal Behavior"
else -> "Unknown"
val reportType = noteEvent.reportType.map {
when (it) {
ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content)
ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity)
ReportEvent.ReportType.PROFANITY -> stringResource(R.string.profanity_hateful_speech)
ReportEvent.ReportType.SPAM -> stringResource(R.string.spam)
ReportEvent.ReportType.IMPERSONATION -> stringResource(R.string.impersonation)
ReportEvent.ReportType.ILLEGAL -> stringResource(R.string.illegal_behavior)
else -> stringResource(R.string.unknown)
}
}.toSet().joinToString(", ")
@ -395,7 +396,7 @@ private fun RelayBadges(baseNote: Note) {
placeholder = BitmapPainter(RoboHashCache.get(ctx, url)),
fallback = 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) }),
modifier = Modifier
.fillMaxSize(1f)
@ -464,7 +465,7 @@ fun NoteAuthorPicture(
if (author == null) {
Image(
painter = BitmapPainter(RoboHashCache.get(ctx, "ohnothisauthorisnotfound")),
contentDescription = "Unknown Author",
contentDescription = stringResource(R.string.unknown_author),
modifier = pictureModifier
.fillMaxSize(1f)
.clip(shape = CircleShape)
@ -509,7 +510,7 @@ fun UserPicture(
AsyncImageProxy(
model = ResizeImage(user.profilePicture(), size),
contentDescription = "Profile Image",
contentDescription = stringResource(id = R.string.profile_image),
placeholder = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)),
fallback = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)),
error = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)),
@ -548,7 +549,7 @@ fun UserPicture(
Icon(
painter = painterResource(R.drawable.ic_verified),
"Following",
stringResource(id = R.string.following),
modifier = Modifier.fillMaxSize(),
tint = Following
)
@ -568,17 +569,17 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
onDismissRequest = 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() }) {
Text("Copy User PubKey")
Text(stringResource(R.string.copy_user_pubkey))
}
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.idNote())); onDismiss() }) {
Text("Copy Note ID")
Text(stringResource(R.string.copy_note_id))
}
Divider()
DropdownMenuItem(onClick = { accountViewModel.broadcast(note); onDismiss() }) {
Text("Broadcast")
Text(stringResource(R.string.broadcast))
}
if (note.author == accountViewModel.accountLiveData.value?.account?.userProfile()) {
Divider()
@ -596,7 +597,7 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
)
}; onDismiss()
}) {
Text("Block & Hide Author")
Text(stringResource(R.string.block_hide_user))
}
Divider()
DropdownMenuItem(onClick = {
@ -604,7 +605,7 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
note.author?.let { accountViewModel.hide(it, context) }
onDismiss()
}) {
Text("Report Spam / Scam")
Text(stringResource(R.string.report_spam_scam))
}
DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.PROFANITY);
@ -618,21 +619,21 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
note.author?.let { accountViewModel.hide(it, context) }
onDismiss()
}) {
Text("Report Impersonation")
Text(stringResource(R.string.report_impersonation))
}
DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.NUDITY);
note.author?.let { accountViewModel.hide(it, context) }
onDismiss()
}) {
Text("Report Nudity")
Text(stringResource(R.string.report_nudity_porn))
}
DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.ILLEGAL);
note.author?.let { accountViewModel.hide(it, context) }
onDismiss()
}) {
Text("Report Illegal Behaviour")
Text(stringResource(R.string.report_illegal_behaviour))
}
}
}

View File

@ -47,6 +47,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
@ -141,7 +142,7 @@ fun ReplyReaction(
scope.launch {
Toast.makeText(
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
).show()
}
@ -193,7 +194,7 @@ private fun BoostReaction(
scope.launch {
Toast.makeText(
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
).show()
}
@ -264,7 +265,7 @@ fun LikeReaction(
scope.launch {
Toast.makeText(
context,
"Login with a Private key to like Posts",
context.getString(R.string.login_with_a_private_key_to_like_posts),
Toast.LENGTH_SHORT
).show()
}
@ -329,7 +330,7 @@ fun ZapReaction(
Toast
.makeText(
context,
"No Zap Amount Setup. Long Press to change",
context.getString(R.string.no_zap_amount_setup_long_press_to_change),
Toast.LENGTH_SHORT
)
.show()
@ -339,7 +340,7 @@ fun ZapReaction(
Toast
.makeText(
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
)
.show()
@ -386,14 +387,14 @@ fun ZapReaction(
if (zappedNote?.isZappedBy(account.userProfile()) == true) {
Icon(
imageVector = Icons.Default.Bolt,
contentDescription = "Zaps",
contentDescription = stringResource(R.string.zaps),
modifier = Modifier.size(20.dp),
tint = BitcoinOrange
)
} else {
Icon(
imageVector = Icons.Outlined.Bolt,
contentDescription = "Zaps",
contentDescription = stringResource(id = R.string.zaps),
modifier = Modifier.size(20.dp),
tint = grayTint
)
@ -433,7 +434,7 @@ private fun ViewCountReaction(baseNote: Note, textModifier: Modifier = Modifier)
.diskCachePolicy(CachePolicy.DISABLED)
.memoryCachePolicy(CachePolicy.ENABLED)
.build(),
contentDescription = "View count",
contentDescription = stringResource(R.string.view_count),
modifier = Modifier.height(24.dp),
colorFilter = ColorFilter.tint(grayTint)
)
@ -461,7 +462,7 @@ private fun BoostTypeChoicePopup(baseNote: Note, accountViewModel: AccountViewMo
backgroundColor = MaterialTheme.colors.primary
)
) {
Text("Boost", color = Color.White, textAlign = TextAlign.Center)
Text(stringResource(R.string.boost), color = Color.White, textAlign = TextAlign.Center)
}
Button(
@ -473,7 +474,7 @@ private fun BoostTypeChoicePopup(baseNote: Note, accountViewModel: AccountViewMo
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) {
OutlinedTextField(
label = { Text(text = "New Amount in Sats") },
label = { Text(text = stringResource(R.string.new_amount_in_sats)) },
value = postViewModel.nextAmount,
onValueChange = {
postViewModel.nextAmount = it
@ -656,7 +657,9 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account) {
)
},
singleLine = true,
modifier = Modifier.padding(end = 10.dp).weight(1f)
modifier = Modifier
.padding(end = 10.dp)
.weight(1f)
)
Button(
@ -667,7 +670,7 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account) {
backgroundColor = MaterialTheme.colors.primary
)
) {
Text(text = "Add", color = Color.White)
Text(text = stringResource(R.string.add), color = Color.White)
}
}
}

View File

@ -17,10 +17,13 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.RelayInfo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import java.time.Instant
@ -38,6 +41,8 @@ fun RelayCompose(
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
val context = LocalContext.current
Column() {
Row(
modifier = Modifier
@ -58,13 +63,13 @@ fun RelayCompose(
)
Text(
timeAgo(relay.lastEvent),
timeAgo(relay.lastEvent, context = context),
maxLines = 1
)
}
Text(
"${relay.counter} posts received",
"${relay.counter} ${stringResource(R.string.posts_received)}",
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
@ -99,7 +104,7 @@ fun AddRelayButton(onClick: () -> Unit) {
),
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)
) {
Text(text = "Remove", color = Color.White)
Text(text = stringResource(R.string.remove), color = Color.White)
}
}

View File

@ -7,6 +7,9 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.remember
import androidx.compose.runtime.setValue
@ -15,6 +18,7 @@ import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.Note
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)
Text(
"replying to ",
stringResource(R.string.replying_to),
fontSize = 13.sp,
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) {
Text(
" and ",
" ${stringResource(R.string.and)} ",
fontSize = 13.sp,
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) {
Text(
" and ",
" ${stringResource(R.string.and)} ",
fontSize = 13.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
@ -88,7 +92,7 @@ fun ReplyInformation(replyTo: List<Note>?, dupMentions: List<User>?, account: Ac
)
Text(
" others",
" ${stringResource(R.string.others)}",
fontSize = 13.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
@ -128,7 +132,7 @@ fun ReplyInformationChannel(replyTo: List<Note>?,
FlowRow() {
Text(
"in channel ",
stringResource(R.string.in_channel),
fontSize = 13.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
@ -142,7 +146,7 @@ fun ReplyInformationChannel(replyTo: List<Note>?,
if (mentions != null && mentions.isNotEmpty()) {
if (replyTo != null && replyTo.isNotEmpty()) {
Text(
"replying to ",
stringResource(id = R.string.replying_to),
fontSize = 13.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
@ -168,7 +172,7 @@ fun ReplyInformationChannel(replyTo: List<Note>?,
)
} else if (idx < mentionSet.size - 1) {
Text(
" and ",
" ${stringResource(id = R.string.add)} ",
fontSize = 13.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)

View File

@ -1,10 +1,12 @@
package com.vitorpamplona.amethyst.ui.note
import android.content.Context
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 == 0L) return "never"
if (mills == 0L) return "${context.getString(R.string.never)}"
var humanReadable = DateUtils.getRelativeTimeSpanString(
mills * 1000,
@ -13,16 +15,16 @@ fun timeAgo(mills: Long?): String {
DateUtils.FORMAT_ABBREV_ALL
).toString()
if (humanReadable.startsWith("In") || humanReadable.startsWith("0")) {
humanReadable = "now";
humanReadable = context.getString(R.string.now);
}
return "" + humanReadable
.replace(" hr. ago", "h")
.replace(" min. ago", "m")
.replace(" days ago", "d")
.replace(" hr. ago", context.getString(R.string.h))
.replace(" min. ago", context.getString(R.string.m))
.replace(" days ago", context.getString(R.string.d))
}
fun timeAgoShort(mills: Long?): String {
fun timeAgoShort(mills: Long?, context: Context): String {
if (mills == null) return " "
var humanReadable = DateUtils.getRelativeTimeSpanString(
@ -32,13 +34,13 @@ fun timeAgoShort(mills: Long?): String {
DateUtils.FORMAT_ABBREV_ALL
).toString()
if (humanReadable.startsWith("In") || humanReadable.startsWith("0")) {
humanReadable = "now";
humanReadable = context.getString(R.string.now);
}
return humanReadable
}
fun timeAgoLong(mills: Long?): String {
fun timeAgoLong(mills: Long?, context: Context): String {
if (mills == null) return " "
var humanReadable = DateUtils.getRelativeTimeSpanString(
@ -48,7 +50,7 @@ fun timeAgoLong(mills: Long?): String {
DateUtils.FORMAT_SHOW_TIME
).toString()
if (humanReadable.startsWith("In") || humanReadable.startsWith("0")) {
humanReadable = "now";
humanReadable = context.getString(R.string.now);
}
return humanReadable

View File

@ -15,11 +15,14 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.model.LnZapEvent
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@ -94,7 +97,7 @@ fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewMode
verticalArrangement = Arrangement.Center
) {
Text(
showAmount(amount) + " sats",
"${showAmount(amount)} ${stringResource(R.string.sats)}",
color = BitcoinOrange,
fontSize = 20.sp,
fontWeight = FontWeight.W500,

View File

@ -24,10 +24,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.ui.screen.ZapSetCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@ -94,9 +96,11 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, modifier: Modifier = Modifier, isInner
.padding(0.dp)) {
Icon(
imageVector = Icons.Default.Bolt,
contentDescription = "Zaps",
contentDescription = stringResource(id = R.string.zaps),
tint = BitcoinOrange,
modifier = Modifier.size(25.dp).align(Alignment.TopEnd)
modifier = Modifier
.size(25.dp)
.align(Alignment.TopEnd)
)
}
}

View File

@ -28,11 +28,15 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
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.model.User
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)),
fallback = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)),
error = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)),
contentDescription = "Profile Image",
contentDescription = stringResource(R.string.profile_image),
modifier = Modifier
.width(100.dp)
.height(100.dp)
@ -129,7 +133,7 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
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()) {
Text(
"Point to the QR Code",
stringResource(R.string.point_to_the_qr_code),
modifier = Modifier.padding(top = 7.dp),
fontWeight = FontWeight.Bold,
fontSize = 25.sp
@ -167,7 +171,7 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
backgroundColor = MaterialTheme.colors.primary
)
) {
Text(text = "Show QR")
Text(text = stringResource(R.string.show_qr))
}
}

View File

@ -22,10 +22,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@ -122,7 +124,7 @@ fun LoadingFeed() {
horizontalAlignment = Alignment.CenterHorizontally,
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,
verticalArrangement = Arrangement.Center,
) {
Text("Error loading replies: $errorMessage")
Text("${stringResource(R.string.error_loading_replies)} $errorMessage")
Button(
modifier = Modifier.align(Alignment.CenterHorizontally),
onClick = onRefresh
) {
Text(text = "Try again")
Text(text = stringResource(R.string.try_again))
}
}
}
@ -154,9 +156,9 @@ fun FeedEmpty(onRefresh: () -> Unit) {
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text("Feed is empty.")
Text(stringResource(R.string.feed_is_empty))
OutlinedButton(onClick = onRefresh) {
Text(text = "Refresh")
Text(text = stringResource(R.string.refresh))
}
}
}

View File

@ -50,6 +50,7 @@ import com.vitorpamplona.amethyst.ui.note.ReactionsRow
import com.vitorpamplona.amethyst.ui.note.timeAgo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.delay
import androidx.compose.ui.platform.LocalContext
@Composable
fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController) {
@ -192,6 +193,8 @@ fun NoteMaster(baseNote: Note,
var showHiddenNote by remember { mutableStateOf(false) }
val context = LocalContext.current
var moreActionsExpanded by remember { mutableStateOf(false) }
val noteEvent = note?.event
@ -232,7 +235,7 @@ fun NoteMaster(baseNote: Note,
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
Text(
timeAgo(noteEvent.createdAt),
timeAgo(noteEvent.createdAt, context = context),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
maxLines = 1
)
@ -286,4 +289,4 @@ fun NoteMaster(baseNote: Note,
}
}
}
}
}

View File

@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.map
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AccountState
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()
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
}

View File

@ -47,6 +47,7 @@ import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardCapitalization
@ -59,6 +60,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.RoboHashCache
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Channel
@ -183,7 +185,7 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
modifier = Modifier.weight(1f, true),
placeholder = {
Text(
text = "reply here.. ",
text = stringResource(R.string.reply_here),
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)),
fallback = 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
.width(35.dp)
.height(35.dp)
@ -302,7 +304,7 @@ private fun NoteCopyButton(
Icon(
tint = Color.White,
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(
@ -310,7 +312,7 @@ private fun NoteCopyButton(
onDismissRequest = { 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(
tint = Color.White,
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)
) {
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)
) {
Text(text = "Leave", color = Color.White)
Text(text = stringResource(R.string.leave), color = Color.White)
}
}

View File

@ -16,6 +16,7 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
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.NostrChatroomListNewFeedViewModel
import kotlinx.coroutines.launch
import com.vitorpamplona.amethyst.R
@OptIn(ExperimentalPagerApi::class)
@Composable
@ -57,7 +59,7 @@ fun ChatroomListScreen(accountViewModel: AccountViewModel, navController: NavCon
selected = pagerState.currentPage == 0,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
text = {
Text(text = "Known")
Text(text = stringResource(R.string.known))
}
)
@ -65,7 +67,7 @@ fun ChatroomListScreen(accountViewModel: AccountViewModel, navController: NavCon
selected = pagerState.currentPage == 1,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
text = {
Text(text = "New Requests")
Text(text = stringResource(R.string.new_requests))
}
)
}

View File

@ -39,6 +39,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.platform.LocalContext
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.TextFieldValue
import androidx.compose.ui.text.style.TextDirection
@ -47,6 +48,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.RoboHashCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
@ -169,7 +171,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
shape = RoundedCornerShape(25.dp),
placeholder = {
Text(
text = "reply here.. ",
text = stringResource(id = R.string.reply_here),
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)),
fallback = 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
.width(35.dp)
.height(35.dp)

View File

@ -13,6 +13,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
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.pagerTabIndicatorOffset
import com.google.accompanist.pager.rememberPagerState
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.dal.HiddenAccountsFeedFilter
import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.UserFeedView
@ -55,7 +57,7 @@ fun FiltersScreen(accountViewModel: AccountViewModel, navController: NavControll
selected = pagerState.currentPage == 0,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
text = {
Text(text = "Blocked Users")
Text(text = stringResource(R.string.blocked_users))
}
)
}

View File

@ -16,6 +16,7 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
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.pagerTabIndicatorOffset
import com.google.accompanist.pager.rememberPagerState
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
@ -74,7 +76,7 @@ fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController)
selected = pagerState.currentPage == 0,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
text = {
Text(text = "New Threads")
Text(text = stringResource(R.string.new_threads))
}
)
@ -82,7 +84,7 @@ fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController)
selected = pagerState.currentPage == 1,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
text = {
Text(text = "Conversations")
Text(text = stringResource(R.string.conversations))
}
)
}

View File

@ -31,6 +31,7 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@ -179,34 +180,34 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
) {
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 userFollows = userState?.user?.follows?.size ?: "--"
Text(text = "$userFollows Follows")
Text(text = "$userFollows ${stringResource(R.string.follows)}")
},
{
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 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 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()
@ -215,7 +216,7 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
val userStateRelayInfo by baseUser.live().relayInfo.observeAsState()
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(
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
imageVector = Icons.Default.MoreVert,
contentDescription = "More Options",
contentDescription = stringResource(R.string.more_options),
)
UserProfileDropDownMenu(baseUser, popupExpanded, { popupExpanded = false }, accountViewModel)
@ -386,7 +387,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account) {
Icon(
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
imageVector = Icons.Default.Link,
contentDescription = "Website",
contentDescription = stringResource(R.string.website),
modifier = Modifier.size(16.dp)
)
@ -408,7 +409,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account) {
Icon(
tint = BitcoinOrange,
imageVector = Icons.Default.Bolt,
contentDescription = "Lightning Address",
contentDescription = stringResource(R.string.lightning_address),
modifier = Modifier.size(16.dp)
)
@ -450,7 +451,7 @@ private fun DrawBanner(baseUser: User) {
if (banner != null && banner.isNotBlank()) {
AsyncImageProxy(
model = ResizeImage(banner, 125.dp),
contentDescription = "Profile Image",
contentDescription = stringResource(id = R.string.profile_image),
contentScale = ContentScale.FillWidth,
modifier = Modifier
.fillMaxWidth()
@ -459,7 +460,7 @@ private fun DrawBanner(baseUser: User) {
} else {
Image(
painter = painterResource(R.drawable.profile_banner),
contentDescription = "Profile Banner",
contentDescription = stringResource(id = R.string.profile_banner),
contentScale = ContentScale.FillWidth,
modifier = Modifier
.fillMaxWidth()
@ -638,7 +639,7 @@ private fun NSecCopyButton(
Icon(
tint = Color.White,
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(
@ -646,7 +647,7 @@ private fun NSecCopyButton(
onDismissRequest = { 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(
tint = Color.White,
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(
@ -681,7 +682,7 @@ private fun NPubCopyButton(
onDismissRequest = { 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(
painter = painterResource(R.drawable.ic_dm),
"Send a Direct Message",
stringResource(R.string.send_a_direct_message),
modifier = Modifier.size(20.dp),
tint = Color.White
)
@ -732,7 +733,7 @@ private fun EditButton(account: Account) {
Icon(
tint = Color.White,
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)
) {
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)
) {
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)
) {
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
) {
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(user.pubkeyNpub())); onDismiss() }) {
Text("Copy User ID")
Text(stringResource(R.string.copy_user_id))
}
if ( account.userProfile() != user) {
@ -813,11 +814,11 @@ fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () ->
)
}; onDismiss()
}) {
Text("Unblock User")
Text(stringResource(R.string.unblock_user))
}
} else {
DropdownMenuItem(onClick = { user.let { accountViewModel.hide(it, context) }; onDismiss() }) {
Text("Block & Hide User")
Text(stringResource(id = R.string.block_hide_user))
}
}
Divider()
@ -826,35 +827,35 @@ fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () ->
user.let { accountViewModel.hide(it, context) }
onDismiss()
}) {
Text("Report Spam / Scam")
Text(stringResource(id = R.string.report_spam_scam))
}
DropdownMenuItem(onClick = {
accountViewModel.report(user, ReportEvent.ReportType.PROFANITY);
user.let { accountViewModel.hide(it, context) }
onDismiss()
}) {
Text("Report Hateful speech")
Text(stringResource(R.string.report_hateful_speech))
}
DropdownMenuItem(onClick = {
accountViewModel.report(user, ReportEvent.ReportType.IMPERSONATION);
user.let { accountViewModel.hide(it, context) }
onDismiss()
}) {
Text("Report Impersonation")
Text(stringResource(id = R.string.report_impersonation))
}
DropdownMenuItem(onClick = {
accountViewModel.report(user, ReportEvent.ReportType.NUDITY);
user.let { accountViewModel.hide(it, context) }
onDismiss()
}) {
Text("Report Nudity / Porn")
Text(stringResource(R.string.report_nudity_porn))
}
DropdownMenuItem(onClick = {
accountViewModel.report(user, ReportEvent.ReportType.ILLEGAL);
user.let { accountViewModel.hide(it, context) }
onDismiss()
}) {
Text("Report Illegal Behaviour")
Text(stringResource(id = R.string.report_illegal_behaviour))
}
}
}

View File

@ -40,6 +40,7 @@ import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextOverflow
@ -202,7 +203,7 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
.defaultMinSize(minHeight = 20.dp),
placeholder = {
Text(
text = "npub, hex, username ",
text = stringResource(R.string.npub_hex_username),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},
@ -220,7 +221,7 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
) {
Icon(
imageVector = Icons.Default.Clear,
contentDescription = "Clear"
contentDescription = stringResource(R.string.clear)
)
}
}

View File

@ -38,8 +38,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
@ -60,6 +62,7 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
val acceptedTerms = remember { mutableStateOf(false) }
var termsAcceptanceIsRequired by remember { mutableStateOf("") }
val uri = LocalUriHandler.current
val context = LocalContext.current
Column(
modifier = Modifier
@ -81,7 +84,7 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
Image(
painterResource(id = R.drawable.amethyst),
contentDescription = "App Logo",
contentDescription = stringResource(R.string.app_logo),
modifier = Modifier.size(200.dp),
contentScale = ContentScale.Inside
)
@ -102,7 +105,7 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
),
placeholder = {
Text(
text = "nsec / npub / hex private key",
text = stringResource(R.string.nsec_npub_hex_private_key),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},
@ -110,7 +113,8 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
IconButton(onClick = { showPassword = !showPassword }) {
Icon(
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 {
accountViewModel.login(key.value.text)
} 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 }
)
Text(text = "I accept the ")
Text(text = stringResource(R.string.i_accept_the))
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") } },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
)
@ -164,18 +168,18 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
Button(
onClick = {
if (!acceptedTerms.value) {
termsAcceptanceIsRequired = "Acceptance of terms is required"
termsAcceptanceIsRequired = context.getString(R.string.acceptance_of_terms_is_required)
}
if (key.value.text.isBlank()) {
errorMessage = "Key is required"
errorMessage = context.getString(R.string.key_is_required)
}
if (acceptedTerms.value && key.value.text.isNotBlank()) {
try {
accountViewModel.login(key.value.text)
} 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
)
) {
Text(text = "Login")
Text(text = stringResource(R.string.login))
}
}
}
// The last child is glued to the bottom.
ClickableText(
text = AnnotatedString("Generate a new key"),
text = AnnotatedString(stringResource(R.string.generate_a_new_key)),
modifier = Modifier
.padding(20.dp)
.fillMaxWidth(),
@ -203,7 +207,7 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
if (acceptedTerms.value) {
accountViewModel.newKey()
} else {
termsAcceptanceIsRequired = "Acceptance of terms is required"
termsAcceptanceIsRequired = context.getString(R.string.acceptance_of_terms_is_required)
}
},
style = TextStyle(

View File

@ -0,0 +1,178 @@
<?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="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>
<string name="nudity">عُري</string>
<string name="profanity_hateful_speech">الألفاظ النابية / خطاب كراهية</string>
<string name="report_hateful_speech">أبلغ عن كلام يحض على الكراهية</string>
<string name="report_nudity_porn">الإبلاغ عن عُري / إباحي</string>
<string name="others">others</string>
</resources>

View File

@ -1,4 +1,180 @@
<resources>
<string name="app_name_release">Amethyst</string>
<string name="app_name_debug">Amethyst Debug</string>
<string name="app_name_release" translatable="false">Amethyst</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="post_not_found">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>
<string name="nudity">Nudity</string>
<string name="profanity_hateful_speech">Profanity / Hateful speech</string>
<string name="report_hateful_speech">Report Hateful speech</string>
<string name="report_nudity_porn">Report Nudity / Porn</string>
<string name="others">others</string>
</resources>