fix lint
Algunas comprobaciones han fallado
Build & Publish APK Release / build (push) Failing after 6m55s
Algunas comprobaciones han fallado
Build & Publish APK Release / build (push) Failing after 6m55s
Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
@@ -31,6 +31,12 @@ android {
|
|||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
}
|
}
|
||||||
|
lint {
|
||||||
|
lintConfig = file("lint.xml")
|
||||||
|
// Warnings do not abort the CI build; only errors do.
|
||||||
|
warningsAsErrors = false
|
||||||
|
abortOnError = true
|
||||||
|
}
|
||||||
packaging {
|
packaging {
|
||||||
resources {
|
resources {
|
||||||
excludes += setOf(
|
excludes += setOf(
|
||||||
@@ -53,8 +59,8 @@ android {
|
|||||||
}
|
}
|
||||||
configurations.all {
|
configurations.all {
|
||||||
resolutionStrategy {
|
resolutionStrategy {
|
||||||
force("org.bouncycastle:bcprov-jdk18on:1.78.1")
|
force("org.bouncycastle:bcprov-jdk18on:1.83")
|
||||||
force("org.bouncycastle:bcpg-jdk18on:1.78.1")
|
force("org.bouncycastle:bcpg-jdk18on:1.83")
|
||||||
}
|
}
|
||||||
// Exclude old BouncyCastle and duplicate xpp3 versions
|
// Exclude old BouncyCastle and duplicate xpp3 versions
|
||||||
exclude(group = "org.bouncycastle", module = "bcprov-jdk15on")
|
exclude(group = "org.bouncycastle", module = "bcprov-jdk15on")
|
||||||
|
|||||||
35
app/lint.xml
Archivo normal
35
app/lint.xml
Archivo normal
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<lint>
|
||||||
|
<!--
|
||||||
|
TrustAllX509TrustManager: comes from smack-core library (not our code).
|
||||||
|
We cannot fix third-party library internals.
|
||||||
|
-->
|
||||||
|
<issue id="TrustAllX509TrustManager" severity="ignore" />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
NewerVersionAvailable: okhttp 5.x is still alpha/breaking change.
|
||||||
|
Intentionally staying on 4.x until stable.
|
||||||
|
-->
|
||||||
|
<issue id="NewerVersionAvailable">
|
||||||
|
<ignore regexp="com\.squareup\.okhttp3:okhttp" />
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
UnusedResources: strings used only through dynamic references in Settings
|
||||||
|
and resources that are part of the public API surface (kept for future use).
|
||||||
|
-->
|
||||||
|
<issue id="UnusedResources">
|
||||||
|
<ignore regexp="R\.string\.settings_" />
|
||||||
|
<ignore regexp="R\.string\.account_status_" />
|
||||||
|
<ignore regexp="R\.string\.contact_status_" />
|
||||||
|
<ignore regexp="R\.string\.permission_" />
|
||||||
|
<ignore regexp="R\.string\.notification_new_message" />
|
||||||
|
<ignore regexp="R\.string\.cd_" />
|
||||||
|
<ignore regexp="R\.string\.nav_contacts" />
|
||||||
|
<ignore regexp="R\.string\.ok|R\.string\.error|R\.string\.loading|R\.string\.retry|R\.string\.search|R\.string\.clear|R\.string\.confirm|R\.string\.open_settings" />
|
||||||
|
<ignore regexp="R\.string\.chat_otr|R\.string\.chat_omemo|R\.string\.chat_stop|R\.string\.chat_media_" />
|
||||||
|
<ignore regexp="R\.string\.browse_rooms|R\.string\.room_participants|R\.string\.room_topic|R\.string\.rooms_search" />
|
||||||
|
<ignore regexp="R\.string\.contact_remove|R\.string\.contact_block" />
|
||||||
|
<ignore regexp="R\.drawable\.ic_launcher_background" />
|
||||||
|
</issue>
|
||||||
|
</lint>
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<!-- required="false" so the app is not excluded from devices without a camera (Chrome OS, etc.) -->
|
||||||
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".AleJabberApp"
|
android:name=".AleJabberApp"
|
||||||
|
|||||||
@@ -32,5 +32,8 @@ interface ContactDao {
|
|||||||
|
|
||||||
@Query("UPDATE contacts SET presence = :presence, statusMessage = :statusMessage WHERE accountId = :accountId AND jid = :jid")
|
@Query("UPDATE contacts SET presence = :presence, statusMessage = :statusMessage WHERE accountId = :accountId AND jid = :jid")
|
||||||
suspend fun updatePresence(accountId: Long, jid: String, presence: String, statusMessage: String)
|
suspend fun updatePresence(accountId: Long, jid: String, presence: String, statusMessage: String)
|
||||||
|
|
||||||
|
@Query("UPDATE contacts SET avatarUrl = :avatarUrl WHERE accountId = :accountId AND jid = :jid")
|
||||||
|
suspend fun updateAvatarUrl(accountId: Long, jid: String, avatarUrl: String?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.manalejandro.alejabber.data.repository
|
package com.manalejandro.alejabber.data.repository
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import android.util.Log
|
||||||
import com.manalejandro.alejabber.data.local.dao.ContactDao
|
import com.manalejandro.alejabber.data.local.dao.ContactDao
|
||||||
import com.manalejandro.alejabber.data.local.entity.toDomain
|
import com.manalejandro.alejabber.data.local.entity.toDomain
|
||||||
import com.manalejandro.alejabber.data.local.entity.toEntity
|
import com.manalejandro.alejabber.data.local.entity.toEntity
|
||||||
@@ -11,6 +13,7 @@ import kotlinx.coroutines.flow.combine
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.jivesoftware.smack.roster.Roster
|
import org.jivesoftware.smack.roster.Roster
|
||||||
import org.jivesoftware.smack.roster.RosterEntry
|
import org.jivesoftware.smack.roster.RosterEntry
|
||||||
|
import org.jivesoftware.smackx.vcardtemp.VCardManager
|
||||||
import org.jxmpp.jid.impl.JidCreate
|
import org.jxmpp.jid.impl.JidCreate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -20,13 +23,13 @@ class ContactRepository @Inject constructor(
|
|||||||
private val contactDao: ContactDao,
|
private val contactDao: ContactDao,
|
||||||
private val xmppManager: XmppConnectionManager
|
private val xmppManager: XmppConnectionManager
|
||||||
) {
|
) {
|
||||||
|
private val TAG = "ContactRepository"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Flow of contacts for [accountId], with **live presence** merged in.
|
* Returns a Flow of contacts for [accountId], with **live presence** merged in.
|
||||||
*
|
*
|
||||||
* Room provides the persisted roster (name, JID, groups).
|
* Room provides the persisted roster (name, JID, groups).
|
||||||
* [XmppConnectionManager.rosterPresence] provides the real-time online/away/offline state.
|
* [XmppConnectionManager.rosterPresence] provides the real-time online/away/offline state.
|
||||||
* The two are combined so the UI always shows the current presence without
|
|
||||||
* writing every presence stanza to the database.
|
|
||||||
*/
|
*/
|
||||||
fun getContacts(accountId: Long): Flow<List<Contact>> =
|
fun getContacts(accountId: Long): Flow<List<Contact>> =
|
||||||
contactDao.getContactsByAccount(accountId)
|
contactDao.getContactsByAccount(accountId)
|
||||||
@@ -34,11 +37,8 @@ class ContactRepository @Inject constructor(
|
|||||||
val accountPresence = presenceMap[accountId] ?: emptyMap()
|
val accountPresence = presenceMap[accountId] ?: emptyMap()
|
||||||
entities.map { entity ->
|
entities.map { entity ->
|
||||||
val livePresence = accountPresence[entity.jid]
|
val livePresence = accountPresence[entity.jid]
|
||||||
if (livePresence != null) {
|
if (livePresence != null) entity.toDomain().copy(presence = livePresence)
|
||||||
entity.toDomain().copy(presence = livePresence)
|
else entity.toDomain()
|
||||||
} else {
|
|
||||||
entity.toDomain()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,9 +54,7 @@ class ContactRepository @Inject constructor(
|
|||||||
roster.createItemAndRequestSubscription(
|
roster.createItemAndRequestSubscription(
|
||||||
jid, contact.nickname.ifBlank { contact.jid }, null
|
jid, contact.nickname.ifBlank { contact.jid }, null
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) { /* save locally anyway */ }
|
||||||
// Proceed to save locally even if the server call fails
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return contactDao.insertContact(contact.toEntity())
|
return contactDao.insertContact(contact.toEntity())
|
||||||
}
|
}
|
||||||
@@ -73,9 +71,18 @@ class ContactRepository @Inject constructor(
|
|||||||
contactDao.deleteContact(accountId, jid)
|
contactDao.deleteContact(accountId, jid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs the server roster to the local DB and then fetches each contact's
|
||||||
|
* vCard avatar in the background. Avatar bytes are stored as a
|
||||||
|
* `data:image/png;base64,…` URI so Coil can display them with no extra
|
||||||
|
* network request.
|
||||||
|
*/
|
||||||
suspend fun syncRoster(accountId: Long) {
|
suspend fun syncRoster(accountId: Long) {
|
||||||
val entries = xmppManager.getRosterEntries(accountId)
|
val connection = xmppManager.getConnection(accountId) ?: return
|
||||||
val contacts = entries.map { entry ->
|
val entries = xmppManager.getRosterEntries(accountId)
|
||||||
|
|
||||||
|
// 1. Upsert the basic roster entries
|
||||||
|
val entities = entries.map { entry ->
|
||||||
Contact(
|
Contact(
|
||||||
accountId = accountId,
|
accountId = accountId,
|
||||||
jid = entry.jid.asBareJid().toString(),
|
jid = entry.jid.asBareJid().toString(),
|
||||||
@@ -83,7 +90,30 @@ class ContactRepository @Inject constructor(
|
|||||||
groups = entry.groups.map { it.name }
|
groups = entry.groups.map { it.name }
|
||||||
).toEntity()
|
).toEntity()
|
||||||
}
|
}
|
||||||
if (contacts.isNotEmpty()) contactDao.insertContacts(contacts)
|
if (entities.isNotEmpty()) contactDao.insertContacts(entities)
|
||||||
|
|
||||||
|
// 2. Fetch vCard avatars for each contact (best-effort, non-blocking errors)
|
||||||
|
if (!connection.isConnected || !connection.isAuthenticated) return
|
||||||
|
val vcardManager = VCardManager.getInstanceFor(connection)
|
||||||
|
entries.forEach { entry ->
|
||||||
|
val bareJid = entry.jid.asBareJid().toString()
|
||||||
|
try {
|
||||||
|
val vcard = vcardManager.loadVCard(
|
||||||
|
JidCreate.entityBareFrom(bareJid)
|
||||||
|
)
|
||||||
|
val avatarBytes = vcard?.avatar
|
||||||
|
if (avatarBytes != null && avatarBytes.isNotEmpty()) {
|
||||||
|
val mime = vcard.avatarMimeType?.ifBlank { "image/png" } ?: "image/png"
|
||||||
|
val b64 = Base64.encodeToString(avatarBytes, Base64.NO_WRAP)
|
||||||
|
val dataUri = "data:$mime;base64,$b64"
|
||||||
|
contactDao.updateAvatarUrl(accountId, bareJid, dataUri)
|
||||||
|
Log.d(TAG, "Avatar loaded for $bareJid (${avatarBytes.size} bytes)")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// vCard not found or server error — keep whatever was stored before
|
||||||
|
Log.d(TAG, "No vCard avatar for $bareJid: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updatePresence(
|
suspend fun updatePresence(
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import javax.inject.Singleton
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class HttpUploadManager @Inject constructor(
|
class HttpUploadManager @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@param:ApplicationContext private val context: Context,
|
||||||
private val xmppManager: XmppConnectionManager,
|
private val xmppManager: XmppConnectionManager,
|
||||||
private val okHttpClient: OkHttpClient
|
private val okHttpClient: OkHttpClient
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -28,23 +28,27 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.ClipboardManager
|
import androidx.compose.ui.platform.LocalClipboard
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.contentDescription
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.LinkAnnotation
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.TextLinkStyles
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withLink
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -76,7 +80,7 @@ fun ChatScreen(
|
|||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val micPermission = rememberPermissionState(Manifest.permission.RECORD_AUDIO)
|
val micPermission = rememberPermissionState(Manifest.permission.RECORD_AUDIO)
|
||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboard = LocalClipboard.current
|
||||||
|
|
||||||
// Message selected via long-press → shows the action bottom sheet
|
// Message selected via long-press → shows the action bottom sheet
|
||||||
var selectedMessage by remember { mutableStateOf<Message?>(null) }
|
var selectedMessage by remember { mutableStateOf<Message?>(null) }
|
||||||
@@ -226,8 +230,8 @@ fun ChatScreen(
|
|||||||
// ── Message action bottom sheet ───────────────────────────────────────
|
// ── Message action bottom sheet ───────────────────────────────────────
|
||||||
selectedMessage?.let { msg ->
|
selectedMessage?.let { msg ->
|
||||||
MessageActionsSheet(
|
MessageActionsSheet(
|
||||||
message = msg,
|
message = msg,
|
||||||
clipboardManager = clipboardManager,
|
clipboard = clipboard,
|
||||||
onDelete = {
|
onDelete = {
|
||||||
selectedMessage = null
|
selectedMessage = null
|
||||||
messageToDelete = msg
|
messageToDelete = msg
|
||||||
@@ -281,24 +285,28 @@ private val URL_PATTERN: Pattern = Pattern.compile(
|
|||||||
"(https?://|www\\.)[\\w\\-]+(\\.[\\w\\-]+)+([\\w.,@?^=%&:/~+#\\-_]*[\\w@?^=%&/~+#\\-_])?"
|
"(https?://|www\\.)[\\w\\-]+(\\.[\\w\\-]+)+([\\w.,@?^=%&:/~+#\\-_]*[\\w@?^=%&/~+#\\-_])?"
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Converts a plain string into an [AnnotatedString] with clickable URL spans. */
|
/** Converts a plain string into an [AnnotatedString] with clickable URL spans (modern API). */
|
||||||
fun buildMessageText(text: String, linkColor: Color): AnnotatedString = buildAnnotatedString {
|
fun buildMessageText(text: String, linkColor: Color): AnnotatedString = buildAnnotatedString {
|
||||||
val matcher = URL_PATTERN.matcher(text)
|
val matcher = URL_PATTERN.matcher(text)
|
||||||
var last = 0
|
var last = 0
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
// Append plain text before the URL
|
|
||||||
append(text.substring(last, matcher.start()))
|
append(text.substring(last, matcher.start()))
|
||||||
val url = matcher.group()
|
val url = matcher.group()
|
||||||
val fullUrl = if (url.startsWith("http")) url else "https://$url"
|
val fullUrl = if (url.startsWith("http")) url else "https://$url"
|
||||||
// Append the URL with a distinct style and a string annotation
|
// Use LinkAnnotation.Url — opens the browser automatically on click.
|
||||||
pushStringAnnotation(tag = "URL", annotation = fullUrl)
|
withLink(
|
||||||
withStyle(SpanStyle(color = linkColor, textDecoration = TextDecoration.Underline)) {
|
LinkAnnotation.Url(
|
||||||
append(url)
|
url = fullUrl,
|
||||||
}
|
styles = TextLinkStyles(
|
||||||
pop()
|
style = SpanStyle(
|
||||||
|
color = linkColor,
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) { append(url) }
|
||||||
last = matcher.end()
|
last = matcher.end()
|
||||||
}
|
}
|
||||||
// Append remaining plain text
|
|
||||||
append(text.substring(last))
|
append(text.substring(last))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,29 +352,16 @@ fun MessageBubble(message: Message, onLongPress: () -> Unit) {
|
|||||||
.padding(horizontal = 14.dp, vertical = 8.dp)
|
.padding(horizontal = 14.dp, vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
when (message.mediaType) {
|
when (message.mediaType) {
|
||||||
MediaType.TEXT, MediaType.LINK, null -> {
|
MediaType.TEXT, MediaType.LINK -> {
|
||||||
// Build annotated text with clickable URLs
|
// Build annotated text with clickable URLs via LinkAnnotation.
|
||||||
|
// Text handles link clicks automatically — no ClickableText needed.
|
||||||
val annotated = remember(message.body) {
|
val annotated = remember(message.body) {
|
||||||
buildMessageText(message.body, linkColor)
|
buildMessageText(message.body, linkColor)
|
||||||
}
|
}
|
||||||
val hasLinks = annotated.getStringAnnotations("URL", 0, annotated.length).isNotEmpty()
|
Text(
|
||||||
if (hasLinks) {
|
text = annotated,
|
||||||
// ClickableText for messages that contain URLs
|
style = MaterialTheme.typography.bodyMedium.copy(color = textColor)
|
||||||
androidx.compose.foundation.text.ClickableText(
|
)
|
||||||
text = annotated,
|
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(color = textColor),
|
|
||||||
onClick = { offset ->
|
|
||||||
annotated.getStringAnnotations("URL", offset, offset)
|
|
||||||
.firstOrNull()?.let { uriHandler.openUri(it.item) }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = message.body,
|
|
||||||
color = textColor,
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
MediaType.IMAGE -> {
|
MediaType.IMAGE -> {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
@@ -457,20 +452,20 @@ fun MessageBubble(message: Message, onLongPress: () -> Unit) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun MessageActionsSheet(
|
fun MessageActionsSheet(
|
||||||
message: Message,
|
message: Message,
|
||||||
clipboardManager: ClipboardManager,
|
clipboard: androidx.compose.ui.platform.Clipboard,
|
||||||
onDelete: () -> Unit,
|
onDelete: () -> Unit,
|
||||||
onDismiss: () -> Unit
|
onDismiss: () -> Unit
|
||||||
) {
|
) {
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
val hasUrl = URL_PATTERN.matcher(message.body).find()
|
val hasUrl = URL_PATTERN.matcher(message.body).find()
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
sheetState = sheetState
|
sheetState = sheetState
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(bottom = 24.dp)) {
|
Column(modifier = Modifier.padding(bottom = 24.dp)) {
|
||||||
// Header preview
|
|
||||||
Text(
|
Text(
|
||||||
text = message.body.take(120) + if (message.body.length > 120) "…" else "",
|
text = message.body.take(120) + if (message.body.length > 120) "…" else "",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
@@ -479,17 +474,21 @@ fun MessageActionsSheet(
|
|||||||
)
|
)
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
|
|
||||||
// ── Copy ──────────────────────────────────────────────────────
|
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text("Copy text") },
|
headlineContent = { Text("Copy text") },
|
||||||
leadingContent = { Icon(Icons.Default.ContentCopy, null) },
|
leadingContent = { Icon(Icons.Default.ContentCopy, null) },
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
clipboardManager.setText(AnnotatedString(message.body))
|
scope.launch {
|
||||||
|
clipboard.setClipEntry(
|
||||||
|
androidx.compose.ui.platform.ClipEntry(
|
||||||
|
android.content.ClipData.newPlainText("message", message.body)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// ── Open URL ─────────────────────────────────────────────────
|
|
||||||
if (hasUrl) {
|
if (hasUrl) {
|
||||||
val matcher = URL_PATTERN.matcher(message.body)
|
val matcher = URL_PATTERN.matcher(message.body)
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
@@ -498,25 +497,23 @@ fun MessageActionsSheet(
|
|||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text("Open link") },
|
headlineContent = { Text("Open link") },
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
Text(
|
Text(fullUrl, style = MaterialTheme.typography.labelSmall,
|
||||||
fullUrl,
|
color = MaterialTheme.colorScheme.primary, maxLines = 1)
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
maxLines = 1
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
leadingContent = { Icon(Icons.Default.OpenInBrowser, null) },
|
leadingContent = { Icon(Icons.Default.OpenInBrowser, null) },
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable { uriHandler.openUri(fullUrl); onDismiss() }
|
||||||
uriHandler.openUri(fullUrl)
|
|
||||||
onDismiss()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
// Copy link separately
|
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text("Copy link") },
|
headlineContent = { Text("Copy link") },
|
||||||
leadingContent = { Icon(Icons.Default.Link, null) },
|
leadingContent = { Icon(Icons.Default.Link, null) },
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
clipboardManager.setText(AnnotatedString(fullUrl))
|
scope.launch {
|
||||||
|
clipboard.setClipEntry(
|
||||||
|
androidx.compose.ui.platform.ClipEntry(
|
||||||
|
android.content.ClipData.newPlainText("link", fullUrl)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -525,17 +522,9 @@ fun MessageActionsSheet(
|
|||||||
|
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
|
|
||||||
// ── Delete ────────────────────────────────────────────────────
|
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = {
|
headlineContent = { Text("Delete message", color = MaterialTheme.colorScheme.error) },
|
||||||
Text("Delete message", color = MaterialTheme.colorScheme.error)
|
leadingContent = { Icon(Icons.Default.DeleteForever, null, tint = MaterialTheme.colorScheme.error) },
|
||||||
},
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.DeleteForever, null,
|
|
||||||
tint = MaterialTheme.colorScheme.error
|
|
||||||
)
|
|
||||||
},
|
|
||||||
modifier = Modifier.clickable { onDelete() }
|
modifier = Modifier.clickable { onDelete() }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
package com.manalejandro.alejabber.ui.components
|
package com.manalejandro.alejabber.ui.components
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.util.Base64
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.semantics.contentDescription
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
@@ -35,26 +39,82 @@ fun AvatarWithStatus(
|
|||||||
contentDescription: String = ""
|
contentDescription: String = ""
|
||||||
) {
|
) {
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
if (!avatarUrl.isNullOrBlank()) {
|
when {
|
||||||
AsyncImage(
|
// ── data URI (vCard Base64 avatar) ────────────────────────────
|
||||||
model = avatarUrl,
|
avatarUrl != null && avatarUrl.startsWith("data:") -> {
|
||||||
contentDescription = contentDescription,
|
DataUriAvatar(
|
||||||
contentScale = ContentScale.Crop,
|
dataUri = avatarUrl,
|
||||||
modifier = Modifier
|
size = size,
|
||||||
.size(size)
|
contentDescription = contentDescription
|
||||||
.clip(CircleShape)
|
)
|
||||||
)
|
}
|
||||||
} else {
|
// ── Regular http/https URL ────────────────────────────────────
|
||||||
InitialsAvatar(name = name, size = size, contentDescription = contentDescription)
|
!avatarUrl.isNullOrBlank() -> {
|
||||||
|
AsyncImage(
|
||||||
|
model = avatarUrl,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(size)
|
||||||
|
.clip(CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// ── No avatar — show initials ─────────────────────────────────
|
||||||
|
else -> {
|
||||||
|
InitialsAvatar(
|
||||||
|
name = name,
|
||||||
|
size = size,
|
||||||
|
contentDescription = contentDescription
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Presence dot
|
// Presence dot (bottom-right)
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomEnd)
|
.align(Alignment.BottomEnd)
|
||||||
.size(size * 0.27f)
|
.size(size * 0.27f)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(presence.toColor())
|
.background(presence.toColor())
|
||||||
.background(MaterialTheme.colorScheme.surface.copy(alpha = 0f))
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes a `data:image/...;base64,...` URI and renders it as a circular avatar.
|
||||||
|
* Uses [remember] to avoid re-decoding on every recomposition.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun DataUriAvatar(
|
||||||
|
dataUri: String,
|
||||||
|
size: Dp,
|
||||||
|
contentDescription: String
|
||||||
|
) {
|
||||||
|
val bitmap = remember(dataUri) {
|
||||||
|
runCatching {
|
||||||
|
// Strip the "data:image/...;base64," prefix
|
||||||
|
val commaIndex = dataUri.indexOf(',')
|
||||||
|
if (commaIndex < 0) return@runCatching null
|
||||||
|
val base64 = dataUri.substring(commaIndex + 1)
|
||||||
|
val bytes = Base64.decode(base64, Base64.DEFAULT)
|
||||||
|
BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bitmap != null) {
|
||||||
|
Image(
|
||||||
|
bitmap = bitmap.asImageBitmap(),
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(size)
|
||||||
|
.clip(CircleShape)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Fallback to initials if decoding failed
|
||||||
|
InitialsAvatar(
|
||||||
|
name = contentDescription,
|
||||||
|
size = size,
|
||||||
|
contentDescription = contentDescription
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,26 +140,26 @@ fun InitialsAvatar(
|
|||||||
.semantics { this.contentDescription = contentDescription }
|
.semantics { this.contentDescription = contentDescription }
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = initials,
|
text = initials,
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
fontSize = (size.value * 0.38f).sp,
|
fontSize = (size.value * 0.38f).sp,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun PresenceStatus.toColor(): Color = when (this) {
|
fun PresenceStatus.toColor(): Color = when (this) {
|
||||||
PresenceStatus.ONLINE -> StatusOnline
|
PresenceStatus.ONLINE -> StatusOnline
|
||||||
PresenceStatus.AWAY, PresenceStatus.XA -> StatusAway
|
PresenceStatus.AWAY, PresenceStatus.XA -> StatusAway
|
||||||
PresenceStatus.DND -> StatusDnd
|
PresenceStatus.DND -> StatusDnd
|
||||||
PresenceStatus.OFFLINE -> StatusOffline
|
PresenceStatus.OFFLINE -> StatusOffline
|
||||||
}
|
}
|
||||||
|
|
||||||
fun PresenceStatus.toLabel(): String = when (this) {
|
fun PresenceStatus.toLabel(): String = when (this) {
|
||||||
PresenceStatus.ONLINE -> "Online"
|
PresenceStatus.ONLINE -> "Online"
|
||||||
PresenceStatus.AWAY -> "Away"
|
PresenceStatus.AWAY -> "Away"
|
||||||
PresenceStatus.XA -> "Extended Away"
|
PresenceStatus.XA -> "Extended Away"
|
||||||
PresenceStatus.DND -> "Do Not Disturb"
|
PresenceStatus.DND -> "Do Not Disturb"
|
||||||
PresenceStatus.OFFLINE -> "Offline"
|
PresenceStatus.OFFLINE -> "Offline"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,14 +135,11 @@ fun ContactsScreen(
|
|||||||
modifier = Modifier.align(Alignment.Center)
|
modifier = Modifier.align(Alignment.Center)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> ContactList(
|
||||||
ContactList(
|
contacts = uiState.filteredContacts,
|
||||||
contacts = uiState.filteredContacts,
|
onContactClick = { onNavigateToChat(accountId, it.jid) },
|
||||||
onContactClick = { onNavigateToChat(accountId, it.jid) },
|
onContactLongPress = { detailContact = it }
|
||||||
onContactLongPress = { detailContact = it },
|
)
|
||||||
onRemoveContact = { removeTarget = it }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,8 +317,7 @@ fun SearchBar(
|
|||||||
fun ContactList(
|
fun ContactList(
|
||||||
contacts: List<Contact>,
|
contacts: List<Contact>,
|
||||||
onContactClick: (Contact) -> Unit,
|
onContactClick: (Contact) -> Unit,
|
||||||
onContactLongPress: (Contact) -> Unit,
|
onContactLongPress: (Contact) -> Unit
|
||||||
onRemoveContact: (Contact) -> Unit
|
|
||||||
) {
|
) {
|
||||||
val presenceOrder = listOf(
|
val presenceOrder = listOf(
|
||||||
PresenceStatus.ONLINE, PresenceStatus.AWAY, PresenceStatus.DND,
|
PresenceStatus.ONLINE, PresenceStatus.AWAY, PresenceStatus.DND,
|
||||||
@@ -351,10 +347,9 @@ fun ContactList(
|
|||||||
}
|
}
|
||||||
items(group, key = { "${presence.name}_${it.jid}" }) { contact ->
|
items(group, key = { "${presence.name}_${it.jid}" }) { contact ->
|
||||||
ContactItem(
|
ContactItem(
|
||||||
contact = contact,
|
contact = contact,
|
||||||
onClick = { onContactClick(contact) },
|
onClick = { onContactClick(contact) },
|
||||||
onLongPress = { onContactLongPress(contact) },
|
onLongPress = { onContactLongPress(contact) }
|
||||||
onRemove = { onRemoveContact(contact) }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,8 +362,7 @@ fun ContactList(
|
|||||||
fun ContactItem(
|
fun ContactItem(
|
||||||
contact: Contact,
|
contact: Contact,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongPress: () -> Unit,
|
onLongPress: () -> Unit
|
||||||
onRemove: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
val displayName = contact.nickname.ifBlank { contact.jid }
|
val displayName = contact.nickname.ifBlank { contact.jid }
|
||||||
|
|
||||||
@@ -389,14 +383,6 @@ fun ContactItem(
|
|||||||
contentDescription = stringResource(R.string.cd_avatar, displayName)
|
contentDescription = stringResource(R.string.cd_avatar, displayName)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
trailingContent = {
|
|
||||||
IconButton(onClick = onRemove) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.PersonRemove, null,
|
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.combinedClickable(onClick = onClick, onLongClick = onLongPress)
|
.combinedClickable(onClick = onClick, onLongClick = onLongPress)
|
||||||
@@ -444,9 +430,9 @@ fun AddContactDialog(
|
|||||||
fun EmptyState(
|
fun EmptyState(
|
||||||
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||||
message: String,
|
message: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
actionLabel: String? = null,
|
actionLabel: String? = null,
|
||||||
onAction: (() -> Unit)? = null,
|
onAction: (() -> Unit)? = null
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.padding(32.dp),
|
modifier = modifier.padding(32.dp),
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ fun JoinRoomDialog(
|
|||||||
onJoin: (Long, String, String, String) -> Unit
|
onJoin: (Long, String, String, String) -> Unit
|
||||||
) {
|
) {
|
||||||
var selectedAccountId by remember {
|
var selectedAccountId by remember {
|
||||||
mutableStateOf(connectedAccounts.firstOrNull()?.id ?: 0L)
|
mutableLongStateOf(connectedAccounts.firstOrNull()?.id ?: 0L)
|
||||||
}
|
}
|
||||||
var roomJid by remember { mutableStateOf("") }
|
var roomJid by remember { mutableStateOf("") }
|
||||||
var nickname by remember { mutableStateOf("") }
|
var nickname by remember { mutableStateOf("") }
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
AleJabber App Icon - Background Layer
|
|
||||||
Deep Indigo gradient background for the adaptive icon.
|
|
||||||
-->
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path
|
|
||||||
android:fillColor="#1A237E"
|
|
||||||
android:pathData="M0,0h108v108h-108z" />
|
|
||||||
<!-- Subtle radial highlight -->
|
|
||||||
<path
|
|
||||||
android:fillColor="#283593"
|
|
||||||
android:pathData="M54,54 m-40,0 a40,40 0 1,0 80,0 a40,40 0 1,0 -80,0" />
|
|
||||||
</vector>
|
|
||||||
@@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">AleJabber</string>
|
<string name="app_name">AleJabber</string>
|
||||||
|
|
||||||
<!-- Navegación -->
|
<!-- Navegación -->
|
||||||
<string name="nav_contacts">Contactos</string>
|
|
||||||
<string name="nav_rooms">Salas</string>
|
<string name="nav_rooms">Salas</string>
|
||||||
<string name="nav_accounts">Cuentas</string>
|
<string name="nav_accounts">Cuentas</string>
|
||||||
<string name="nav_settings">Ajustes</string>
|
<string name="nav_settings">Ajustes</string>
|
||||||
@@ -17,12 +17,6 @@
|
|||||||
<string name="account_server">Servidor</string>
|
<string name="account_server">Servidor</string>
|
||||||
<string name="account_port">Puerto</string>
|
<string name="account_port">Puerto</string>
|
||||||
<string name="account_use_tls">Usar TLS</string>
|
<string name="account_use_tls">Usar TLS</string>
|
||||||
<string name="account_status_online">Conectado</string>
|
|
||||||
<string name="account_status_offline">Desconectado</string>
|
|
||||||
<string name="account_status_connecting">Conectando…</string>
|
|
||||||
<string name="account_status_error">Error de conexión</string>
|
|
||||||
<string name="account_connect">Conectar</string>
|
|
||||||
<string name="account_disconnect">Desconectar</string>
|
|
||||||
<string name="account_no_accounts">No hay cuentas configuradas.\nPulsa + para añadir una.</string>
|
<string name="account_no_accounts">No hay cuentas configuradas.\nPulsa + para añadir una.</string>
|
||||||
<string name="account_delete_confirm">¿Eliminar la cuenta %1$s?</string>
|
<string name="account_delete_confirm">¿Eliminar la cuenta %1$s?</string>
|
||||||
<string name="account_jid_hint">usuario@ejemplo.com</string>
|
<string name="account_jid_hint">usuario@ejemplo.com</string>
|
||||||
@@ -32,42 +26,27 @@
|
|||||||
<!-- Contactos -->
|
<!-- Contactos -->
|
||||||
<string name="contacts_title">Contactos</string>
|
<string name="contacts_title">Contactos</string>
|
||||||
<string name="contacts_search">Buscar contactos…</string>
|
<string name="contacts_search">Buscar contactos…</string>
|
||||||
<string name="contacts_empty">Sin contactos.\nAñade personas con su ID de Jabber.</string>
|
<string name="contacts_empty">Aún no hay contactos.\nAñade personas con su Jabber ID.</string>
|
||||||
<string name="add_contact">Añadir contacto</string>
|
<string name="add_contact">Añadir contacto</string>
|
||||||
<string name="contact_jid">ID de Jabber (JID)</string>
|
<string name="contact_jid">Jabber ID (JID)</string>
|
||||||
<string name="contact_nickname">Apodo</string>
|
<string name="contact_nickname">Apodo</string>
|
||||||
<string name="contact_remove">Eliminar contacto</string>
|
|
||||||
<string name="contact_block">Bloquear contacto</string>
|
|
||||||
<string name="contact_status_online">En línea</string>
|
|
||||||
<string name="contact_status_away">Ausente</string>
|
|
||||||
<string name="contact_status_dnd">No molestar</string>
|
|
||||||
<string name="contact_status_offline">Desconectado</string>
|
|
||||||
|
|
||||||
<!-- Salas / MUC -->
|
<!-- Salas / MUC -->
|
||||||
<string name="rooms_title">Salas</string>
|
<string name="rooms_title">Salas</string>
|
||||||
<string name="rooms_search">Buscar salas…</string>
|
<string name="rooms_empty">Aún no te has unido a ninguna sala.</string>
|
||||||
<string name="rooms_empty">No te has unido a ninguna sala.</string>
|
|
||||||
<string name="join_room">Unirse a sala</string>
|
<string name="join_room">Unirse a sala</string>
|
||||||
<string name="room_jid">JID de la sala</string>
|
<string name="room_jid">JID de la sala</string>
|
||||||
<string name="room_nickname">Tu apodo</string>
|
<string name="room_nickname">Tu apodo</string>
|
||||||
<string name="room_password">Contraseña (opcional)</string>
|
<string name="room_password">Contraseña de la sala</string>
|
||||||
<string name="leave_room">Salir de la sala</string>
|
<string name="leave_room">Salir de la sala</string>
|
||||||
<string name="room_participants">Participantes</string>
|
|
||||||
<string name="room_topic">Tema</string>
|
|
||||||
<string name="browse_rooms">Explorar salas</string>
|
|
||||||
|
|
||||||
<!-- Chat -->
|
<!-- Chat -->
|
||||||
<string name="chat_hint">Escribe un mensaje…</string>
|
<string name="chat_hint">Escribe un mensaje…</string>
|
||||||
<string name="chat_send">Enviar</string>
|
<string name="chat_send">Enviar</string>
|
||||||
<string name="chat_attach">Adjuntar archivo</string>
|
<string name="chat_attach">Adjuntar archivo</string>
|
||||||
<string name="chat_record_audio">Grabar audio</string>
|
<string name="chat_record_audio">Grabar audio</string>
|
||||||
<string name="chat_stop_recording">Detener grabación</string>
|
|
||||||
<string name="chat_send_audio">Enviar audio</string>
|
<string name="chat_send_audio">Enviar audio</string>
|
||||||
<string name="chat_cancel_audio">Cancelar audio</string>
|
<string name="chat_cancel_audio">Cancelar audio</string>
|
||||||
<string name="chat_encryption_none">Sin cifrado</string>
|
|
||||||
<string name="chat_encryption_otr">OTR</string>
|
|
||||||
<string name="chat_encryption_omemo">OMEMO</string>
|
|
||||||
<string name="chat_encryption_pgp">OpenPGP</string>
|
|
||||||
<string name="chat_encryption_select">Seleccionar cifrado</string>
|
<string name="chat_encryption_select">Seleccionar cifrado</string>
|
||||||
<string name="chat_message_delivered">Entregado</string>
|
<string name="chat_message_delivered">Entregado</string>
|
||||||
<string name="chat_message_read">Leído</string>
|
<string name="chat_message_read">Leído</string>
|
||||||
@@ -75,17 +54,9 @@
|
|||||||
<string name="chat_message_failed">Error al enviar</string>
|
<string name="chat_message_failed">Error al enviar</string>
|
||||||
<string name="chat_typing">%1$s está escribiendo…</string>
|
<string name="chat_typing">%1$s está escribiendo…</string>
|
||||||
<string name="chat_media_image">Imagen</string>
|
<string name="chat_media_image">Imagen</string>
|
||||||
<string name="chat_media_video">Vídeo</string>
|
|
||||||
<string name="chat_media_audio">Audio</string>
|
<string name="chat_media_audio">Audio</string>
|
||||||
<string name="chat_media_file">Archivo</string>
|
<string name="chat_media_file">Archivo</string>
|
||||||
<string name="chat_media_uploading">Subiendo…</string>
|
<string name="chat_empty">Aún no hay mensajes.\n¡Di hola!</string>
|
||||||
<string name="chat_media_download">Descargar</string>
|
|
||||||
<string name="chat_empty">Sin mensajes aún.\n¡Di hola!</string>
|
|
||||||
<string name="chat_otr_started">Sesión OTR iniciada. La conversación está cifrada.</string>
|
|
||||||
<string name="chat_otr_ended">Sesión OTR finalizada.</string>
|
|
||||||
<string name="chat_otr_untrusted">Advertencia: Huella OTR no verificada.</string>
|
|
||||||
<string name="chat_omemo_trusted">OMEMO: Todos los dispositivos son de confianza.</string>
|
|
||||||
<string name="chat_omemo_untrusted">OMEMO: Dispositivos no verificados detectados.</string>
|
|
||||||
|
|
||||||
<!-- Ajustes -->
|
<!-- Ajustes -->
|
||||||
<string name="settings_title">Ajustes</string>
|
<string name="settings_title">Ajustes</string>
|
||||||
@@ -95,62 +66,31 @@
|
|||||||
<string name="settings_theme_light">Claro</string>
|
<string name="settings_theme_light">Claro</string>
|
||||||
<string name="settings_theme_dark">Oscuro</string>
|
<string name="settings_theme_dark">Oscuro</string>
|
||||||
<string name="settings_language">Idioma</string>
|
<string name="settings_language">Idioma</string>
|
||||||
<string name="settings_language_en">English</string>
|
|
||||||
<string name="settings_language_es">Español</string>
|
|
||||||
<string name="settings_language_zh">中文</string>
|
|
||||||
<string name="settings_notifications">Notificaciones</string>
|
<string name="settings_notifications">Notificaciones</string>
|
||||||
<string name="settings_notifications_messages">Notificaciones de mensajes</string>
|
<string name="settings_notifications_messages">Notificaciones de mensajes</string>
|
||||||
<string name="settings_notifications_vibrate">Vibrar</string>
|
<string name="settings_notifications_vibrate">Vibrar</string>
|
||||||
<string name="settings_notifications_sound">Sonido</string>
|
<string name="settings_notifications_sound">Sonido</string>
|
||||||
<string name="settings_encryption">Cifrado</string>
|
<string name="settings_encryption">Cifrado</string>
|
||||||
<string name="settings_omemo_devices">Dispositivos OMEMO</string>
|
|
||||||
<string name="settings_pgp_keys">Claves OpenPGP</string>
|
|
||||||
<string name="settings_otr_fingerprints">Huellas OTR</string>
|
|
||||||
<string name="settings_default_encryption">Cifrado por defecto</string>
|
<string name="settings_default_encryption">Cifrado por defecto</string>
|
||||||
<string name="settings_about">Acerca de</string>
|
<string name="settings_about">Acerca de</string>
|
||||||
<string name="settings_version">Versión</string>
|
<string name="settings_version">Versión</string>
|
||||||
|
|
||||||
<!-- Común -->
|
<!-- Común -->
|
||||||
<string name="ok">Aceptar</string>
|
|
||||||
<string name="cancel">Cancelar</string>
|
<string name="cancel">Cancelar</string>
|
||||||
<string name="save">Guardar</string>
|
<string name="save">Guardar</string>
|
||||||
<string name="delete">Eliminar</string>
|
<string name="delete">Eliminar</string>
|
||||||
<string name="confirm">Confirmar</string>
|
|
||||||
<string name="error">Error</string>
|
|
||||||
<string name="loading">Cargando…</string>
|
|
||||||
<string name="retry">Reintentar</string>
|
|
||||||
<string name="close">Cerrar</string>
|
<string name="close">Cerrar</string>
|
||||||
<string name="search">Buscar</string>
|
|
||||||
<string name="clear">Limpiar</string>
|
|
||||||
<string name="back">Atrás</string>
|
<string name="back">Atrás</string>
|
||||||
<string name="more_options">Más opciones</string>
|
<string name="more_options">Más opciones</string>
|
||||||
|
|
||||||
<!-- Permisos -->
|
|
||||||
<string name="permission_microphone_title">Permiso de micrófono</string>
|
|
||||||
<string name="permission_microphone_message">AleJabber necesita acceso al micrófono para grabar mensajes de audio.</string>
|
|
||||||
<string name="permission_storage_title">Permiso de almacenamiento</string>
|
|
||||||
<string name="permission_storage_message">AleJabber necesita acceso al almacenamiento para enviar y recibir archivos.</string>
|
|
||||||
<string name="permission_camera_title">Permiso de cámara</string>
|
|
||||||
<string name="permission_camera_message">AleJabber necesita acceso a la cámara para tomar fotos.</string>
|
|
||||||
<string name="permission_denied">Permiso denegado. Por favor, concédelo en Ajustes.</string>
|
|
||||||
<string name="open_settings">Abrir Ajustes</string>
|
|
||||||
|
|
||||||
<!-- Notificaciones -->
|
<!-- Notificaciones -->
|
||||||
<string name="notification_channel_messages">Mensajes</string>
|
<string name="notification_channel_messages">Mensajes</string>
|
||||||
<string name="notification_channel_messages_desc">Mensajes de chat entrantes</string>
|
<string name="notification_channel_messages_desc">Mensajes entrantes</string>
|
||||||
<string name="notification_channel_service">Servicio XMPP</string>
|
<string name="notification_channel_service">Servicio XMPP</string>
|
||||||
<string name="notification_channel_service_desc">Conexión XMPP en segundo plano</string>
|
<string name="notification_channel_service_desc">Conexión XMPP en segundo plano</string>
|
||||||
<string name="notification_service_running">AleJabber está conectado</string>
|
<string name="notification_service_running">AleJabber está conectado</string>
|
||||||
<string name="notification_new_message">Nuevo mensaje de %1$s</string>
|
|
||||||
|
|
||||||
<!-- Accesibilidad -->
|
<!-- Accesibilidad -->
|
||||||
<string name="cd_avatar">Avatar de %1$s</string>
|
<string name="cd_avatar">Avatar de %1$s</string>
|
||||||
<string name="cd_status_indicator">Estado: %1$s</string>
|
|
||||||
<string name="cd_encryption_badge">Cifrado: %1$s</string>
|
<string name="cd_encryption_badge">Cifrado: %1$s</string>
|
||||||
<string name="cd_send_button">Enviar mensaje</string>
|
|
||||||
<string name="cd_attach_button">Adjuntar archivo</string>
|
|
||||||
<string name="cd_record_button">Grabar mensaje de audio</string>
|
|
||||||
<string name="cd_message_delivered">Mensaje entregado</string>
|
|
||||||
<string name="cd_message_read">Mensaje leído</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">AleJabber</string>
|
<string name="app_name">AleJabber</string>
|
||||||
|
|
||||||
<!-- 导航 -->
|
<!-- 导航 -->
|
||||||
<string name="nav_contacts">联系人</string>
|
<string name="nav_rooms">房间</string>
|
||||||
<string name="nav_rooms">聊天室</string>
|
|
||||||
<string name="nav_accounts">账号</string>
|
<string name="nav_accounts">账号</string>
|
||||||
<string name="nav_settings">设置</string>
|
<string name="nav_settings">设置</string>
|
||||||
|
|
||||||
<!-- 账号 -->
|
<!-- 账号 -->
|
||||||
<string name="accounts_title">账号管理</string>
|
<string name="accounts_title">账号</string>
|
||||||
<string name="add_account">添加账号</string>
|
<string name="add_account">添加账号</string>
|
||||||
<string name="edit_account">编辑账号</string>
|
<string name="edit_account">编辑账号</string>
|
||||||
<string name="delete_account">删除账号</string>
|
<string name="delete_account">删除账号</string>
|
||||||
@@ -17,13 +17,7 @@
|
|||||||
<string name="account_server">服务器</string>
|
<string name="account_server">服务器</string>
|
||||||
<string name="account_port">端口</string>
|
<string name="account_port">端口</string>
|
||||||
<string name="account_use_tls">使用 TLS</string>
|
<string name="account_use_tls">使用 TLS</string>
|
||||||
<string name="account_status_online">已连接</string>
|
<string name="account_no_accounts">没有已配置的账号。\n点击 + 添加一个。</string>
|
||||||
<string name="account_status_offline">已断开</string>
|
|
||||||
<string name="account_status_connecting">连接中…</string>
|
|
||||||
<string name="account_status_error">连接错误</string>
|
|
||||||
<string name="account_connect">连接</string>
|
|
||||||
<string name="account_disconnect">断开连接</string>
|
|
||||||
<string name="account_no_accounts">尚未配置账号。\n点击 + 添加账号。</string>
|
|
||||||
<string name="account_delete_confirm">删除账号 %1$s?</string>
|
<string name="account_delete_confirm">删除账号 %1$s?</string>
|
||||||
<string name="account_jid_hint">user@example.com</string>
|
<string name="account_jid_hint">user@example.com</string>
|
||||||
<string name="account_resource">资源</string>
|
<string name="account_resource">资源</string>
|
||||||
@@ -32,42 +26,27 @@
|
|||||||
<!-- 联系人 -->
|
<!-- 联系人 -->
|
||||||
<string name="contacts_title">联系人</string>
|
<string name="contacts_title">联系人</string>
|
||||||
<string name="contacts_search">搜索联系人…</string>
|
<string name="contacts_search">搜索联系人…</string>
|
||||||
<string name="contacts_empty">暂无联系人。\n通过 Jabber ID 添加好友。</string>
|
<string name="contacts_empty">还没有联系人。\n使用 Jabber ID 添加朋友。</string>
|
||||||
<string name="add_contact">添加联系人</string>
|
<string name="add_contact">添加联系人</string>
|
||||||
<string name="contact_jid">Jabber ID (JID)</string>
|
<string name="contact_jid">Jabber ID (JID)</string>
|
||||||
<string name="contact_nickname">昵称</string>
|
<string name="contact_nickname">昵称</string>
|
||||||
<string name="contact_remove">删除联系人</string>
|
|
||||||
<string name="contact_block">屏蔽联系人</string>
|
|
||||||
<string name="contact_status_online">在线</string>
|
|
||||||
<string name="contact_status_away">离开</string>
|
|
||||||
<string name="contact_status_dnd">请勿打扰</string>
|
|
||||||
<string name="contact_status_offline">离线</string>
|
|
||||||
|
|
||||||
<!-- 聊天室 / MUC -->
|
<!-- 房间 / MUC -->
|
||||||
<string name="rooms_title">聊天室</string>
|
<string name="rooms_title">房间</string>
|
||||||
<string name="rooms_search">搜索聊天室…</string>
|
<string name="rooms_empty">还没有加入任何房间。</string>
|
||||||
<string name="rooms_empty">尚未加入任何聊天室。</string>
|
<string name="join_room">加入房间</string>
|
||||||
<string name="join_room">加入聊天室</string>
|
<string name="room_jid">房间 JID</string>
|
||||||
<string name="room_jid">聊天室 JID</string>
|
<string name="room_nickname">你的昵称</string>
|
||||||
<string name="room_nickname">您的昵称</string>
|
<string name="room_password">房间密码</string>
|
||||||
<string name="room_password">聊天室密码(可选)</string>
|
<string name="leave_room">离开房间</string>
|
||||||
<string name="leave_room">退出聊天室</string>
|
|
||||||
<string name="room_participants">参与者</string>
|
|
||||||
<string name="room_topic">主题</string>
|
|
||||||
<string name="browse_rooms">浏览聊天室</string>
|
|
||||||
|
|
||||||
<!-- 聊天 -->
|
<!-- 聊天 -->
|
||||||
<string name="chat_hint">输入消息…</string>
|
<string name="chat_hint">输入消息…</string>
|
||||||
<string name="chat_send">发送</string>
|
<string name="chat_send">发送</string>
|
||||||
<string name="chat_attach">附件</string>
|
<string name="chat_attach">附加文件</string>
|
||||||
<string name="chat_record_audio">录音</string>
|
<string name="chat_record_audio">录制音频</string>
|
||||||
<string name="chat_stop_recording">停止录音</string>
|
|
||||||
<string name="chat_send_audio">发送音频</string>
|
<string name="chat_send_audio">发送音频</string>
|
||||||
<string name="chat_cancel_audio">取消录音</string>
|
<string name="chat_cancel_audio">取消录音</string>
|
||||||
<string name="chat_encryption_none">无加密</string>
|
|
||||||
<string name="chat_encryption_otr">OTR</string>
|
|
||||||
<string name="chat_encryption_omemo">OMEMO</string>
|
|
||||||
<string name="chat_encryption_pgp">OpenPGP</string>
|
|
||||||
<string name="chat_encryption_select">选择加密方式</string>
|
<string name="chat_encryption_select">选择加密方式</string>
|
||||||
<string name="chat_message_delivered">已送达</string>
|
<string name="chat_message_delivered">已送达</string>
|
||||||
<string name="chat_message_read">已读</string>
|
<string name="chat_message_read">已读</string>
|
||||||
@@ -75,17 +54,9 @@
|
|||||||
<string name="chat_message_failed">发送失败</string>
|
<string name="chat_message_failed">发送失败</string>
|
||||||
<string name="chat_typing">%1$s 正在输入…</string>
|
<string name="chat_typing">%1$s 正在输入…</string>
|
||||||
<string name="chat_media_image">图片</string>
|
<string name="chat_media_image">图片</string>
|
||||||
<string name="chat_media_video">视频</string>
|
|
||||||
<string name="chat_media_audio">音频</string>
|
<string name="chat_media_audio">音频</string>
|
||||||
<string name="chat_media_file">文件</string>
|
<string name="chat_media_file">文件</string>
|
||||||
<string name="chat_media_uploading">上传中…</string>
|
<string name="chat_empty">还没有消息。\n说声你好吧!</string>
|
||||||
<string name="chat_media_download">下载</string>
|
|
||||||
<string name="chat_empty">暂无消息。\n说声你好!</string>
|
|
||||||
<string name="chat_otr_started">OTR 会话已开始,您的对话已加密。</string>
|
|
||||||
<string name="chat_otr_ended">OTR 会话已结束。</string>
|
|
||||||
<string name="chat_otr_untrusted">警告:OTR 指纹未验证。</string>
|
|
||||||
<string name="chat_omemo_trusted">OMEMO:所有设备已信任。</string>
|
|
||||||
<string name="chat_omemo_untrusted">OMEMO:检测到不受信任的设备。</string>
|
|
||||||
|
|
||||||
<!-- 设置 -->
|
<!-- 设置 -->
|
||||||
<string name="settings_title">设置</string>
|
<string name="settings_title">设置</string>
|
||||||
@@ -95,62 +66,31 @@
|
|||||||
<string name="settings_theme_light">浅色</string>
|
<string name="settings_theme_light">浅色</string>
|
||||||
<string name="settings_theme_dark">深色</string>
|
<string name="settings_theme_dark">深色</string>
|
||||||
<string name="settings_language">语言</string>
|
<string name="settings_language">语言</string>
|
||||||
<string name="settings_language_en">English</string>
|
|
||||||
<string name="settings_language_es">Español</string>
|
|
||||||
<string name="settings_language_zh">中文</string>
|
|
||||||
<string name="settings_notifications">通知</string>
|
<string name="settings_notifications">通知</string>
|
||||||
<string name="settings_notifications_messages">消息通知</string>
|
<string name="settings_notifications_messages">消息通知</string>
|
||||||
<string name="settings_notifications_vibrate">震动</string>
|
<string name="settings_notifications_vibrate">振动</string>
|
||||||
<string name="settings_notifications_sound">声音</string>
|
<string name="settings_notifications_sound">声音</string>
|
||||||
<string name="settings_encryption">加密</string>
|
<string name="settings_encryption">加密</string>
|
||||||
<string name="settings_omemo_devices">OMEMO 设备</string>
|
|
||||||
<string name="settings_pgp_keys">OpenPGP 密钥</string>
|
|
||||||
<string name="settings_otr_fingerprints">OTR 指纹</string>
|
|
||||||
<string name="settings_default_encryption">默认加密方式</string>
|
<string name="settings_default_encryption">默认加密方式</string>
|
||||||
<string name="settings_about">关于</string>
|
<string name="settings_about">关于</string>
|
||||||
<string name="settings_version">版本</string>
|
<string name="settings_version">版本</string>
|
||||||
|
|
||||||
<!-- 通用 -->
|
<!-- 通用 -->
|
||||||
<string name="ok">确定</string>
|
|
||||||
<string name="cancel">取消</string>
|
<string name="cancel">取消</string>
|
||||||
<string name="save">保存</string>
|
<string name="save">保存</string>
|
||||||
<string name="delete">删除</string>
|
<string name="delete">删除</string>
|
||||||
<string name="confirm">确认</string>
|
|
||||||
<string name="error">错误</string>
|
|
||||||
<string name="loading">加载中…</string>
|
|
||||||
<string name="retry">重试</string>
|
|
||||||
<string name="close">关闭</string>
|
<string name="close">关闭</string>
|
||||||
<string name="search">搜索</string>
|
|
||||||
<string name="clear">清除</string>
|
|
||||||
<string name="back">返回</string>
|
<string name="back">返回</string>
|
||||||
<string name="more_options">更多选项</string>
|
<string name="more_options">更多选项</string>
|
||||||
|
|
||||||
<!-- 权限 -->
|
|
||||||
<string name="permission_microphone_title">麦克风权限</string>
|
|
||||||
<string name="permission_microphone_message">AleJabber 需要麦克风权限来录制语音消息。</string>
|
|
||||||
<string name="permission_storage_title">存储权限</string>
|
|
||||||
<string name="permission_storage_message">AleJabber 需要存储权限来发送和接收文件。</string>
|
|
||||||
<string name="permission_camera_title">相机权限</string>
|
|
||||||
<string name="permission_camera_message">AleJabber 需要相机权限来拍照。</string>
|
|
||||||
<string name="permission_denied">权限被拒绝,请在设置中授予权限。</string>
|
|
||||||
<string name="open_settings">打开设置</string>
|
|
||||||
|
|
||||||
<!-- 通知 -->
|
<!-- 通知 -->
|
||||||
<string name="notification_channel_messages">消息</string>
|
<string name="notification_channel_messages">消息</string>
|
||||||
<string name="notification_channel_messages_desc">收到的聊天消息</string>
|
<string name="notification_channel_messages_desc">传入的聊天消息</string>
|
||||||
<string name="notification_channel_service">XMPP 服务</string>
|
<string name="notification_channel_service">XMPP 服务</string>
|
||||||
<string name="notification_channel_service_desc">后台 XMPP 连接</string>
|
<string name="notification_channel_service_desc">后台 XMPP 连接</string>
|
||||||
<string name="notification_service_running">AleJabber 已连接</string>
|
<string name="notification_service_running">AleJabber 已连接</string>
|
||||||
<string name="notification_new_message">来自 %1$s 的新消息</string>
|
|
||||||
|
|
||||||
<!-- 无障碍 -->
|
<!-- 无障碍 -->
|
||||||
<string name="cd_avatar">%1$s 的头像</string>
|
<string name="cd_avatar">%1$s 的头像</string>
|
||||||
<string name="cd_status_indicator">状态:%1$s</string>
|
|
||||||
<string name="cd_encryption_badge">加密:%1$s</string>
|
<string name="cd_encryption_badge">加密:%1$s</string>
|
||||||
<string name="cd_send_button">发送消息</string>
|
|
||||||
<string name="cd_attach_button">附件</string>
|
|
||||||
<string name="cd_record_button">录制语音消息</string>
|
|
||||||
<string name="cd_message_delivered">消息已送达</string>
|
|
||||||
<string name="cd_message_read">消息已读</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="purple_200">#FFBB86FC</color>
|
<!-- Launcher icon background color -->
|
||||||
<color name="purple_500">#FF6200EE</color>
|
<color name="ic_launcher_background">#FF1E3A8A</color>
|
||||||
<color name="purple_700">#FF3700B3</color>
|
|
||||||
<color name="teal_200">#FF03DAC5</color>
|
|
||||||
<color name="teal_700">#FF018786</color>
|
|
||||||
<color name="black">#FF000000</color>
|
|
||||||
<color name="white">#FFFFFFFF</color>
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="ic_launcher_background">#24308B</color>
|
|
||||||
</resources>
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
<string name="app_name">AleJabber</string>
|
<string name="app_name">AleJabber</string>
|
||||||
|
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<string name="nav_contacts">Contacts</string>
|
|
||||||
<string name="nav_rooms">Rooms</string>
|
<string name="nav_rooms">Rooms</string>
|
||||||
<string name="nav_accounts">Accounts</string>
|
<string name="nav_accounts">Accounts</string>
|
||||||
<string name="nav_settings">Settings</string>
|
<string name="nav_settings">Settings</string>
|
||||||
@@ -17,12 +16,6 @@
|
|||||||
<string name="account_server">Server</string>
|
<string name="account_server">Server</string>
|
||||||
<string name="account_port">Port</string>
|
<string name="account_port">Port</string>
|
||||||
<string name="account_use_tls">Use TLS</string>
|
<string name="account_use_tls">Use TLS</string>
|
||||||
<string name="account_status_online">Online</string>
|
|
||||||
<string name="account_status_offline">Offline</string>
|
|
||||||
<string name="account_status_connecting">Connecting…</string>
|
|
||||||
<string name="account_status_error">Connection Error</string>
|
|
||||||
<string name="account_connect">Connect</string>
|
|
||||||
<string name="account_disconnect">Disconnect</string>
|
|
||||||
<string name="account_no_accounts">No accounts configured.\nTap + to add one.</string>
|
<string name="account_no_accounts">No accounts configured.\nTap + to add one.</string>
|
||||||
<string name="account_delete_confirm">Delete account %1$s?</string>
|
<string name="account_delete_confirm">Delete account %1$s?</string>
|
||||||
<string name="account_jid_hint">user@example.com</string>
|
<string name="account_jid_hint">user@example.com</string>
|
||||||
@@ -36,38 +29,23 @@
|
|||||||
<string name="add_contact">Add Contact</string>
|
<string name="add_contact">Add Contact</string>
|
||||||
<string name="contact_jid">Jabber ID (JID)</string>
|
<string name="contact_jid">Jabber ID (JID)</string>
|
||||||
<string name="contact_nickname">Nickname</string>
|
<string name="contact_nickname">Nickname</string>
|
||||||
<string name="contact_remove">Remove Contact</string>
|
|
||||||
<string name="contact_block">Block Contact</string>
|
|
||||||
<string name="contact_status_online">Online</string>
|
|
||||||
<string name="contact_status_away">Away</string>
|
|
||||||
<string name="contact_status_dnd">Do Not Disturb</string>
|
|
||||||
<string name="contact_status_offline">Offline</string>
|
|
||||||
|
|
||||||
<!-- Rooms / MUC -->
|
<!-- Rooms / MUC -->
|
||||||
<string name="rooms_title">Rooms</string>
|
<string name="rooms_title">Rooms</string>
|
||||||
<string name="rooms_search">Search rooms…</string>
|
|
||||||
<string name="rooms_empty">No rooms joined yet.</string>
|
<string name="rooms_empty">No rooms joined yet.</string>
|
||||||
<string name="join_room">Join Room</string>
|
<string name="join_room">Join Room</string>
|
||||||
<string name="room_jid">Room JID</string>
|
<string name="room_jid">Room JID</string>
|
||||||
<string name="room_nickname">Your Nickname</string>
|
<string name="room_nickname">Your Nickname</string>
|
||||||
<string name="room_password">Room Password (optional)</string>
|
<string name="room_password">Room Password</string>
|
||||||
<string name="leave_room">Leave Room</string>
|
<string name="leave_room">Leave Room</string>
|
||||||
<string name="room_participants">Participants</string>
|
|
||||||
<string name="room_topic">Topic</string>
|
|
||||||
<string name="browse_rooms">Browse Rooms</string>
|
|
||||||
|
|
||||||
<!-- Chat -->
|
<!-- Chat -->
|
||||||
<string name="chat_hint">Type a message…</string>
|
<string name="chat_hint">Type a message…</string>
|
||||||
<string name="chat_send">Send</string>
|
<string name="chat_send">Send</string>
|
||||||
<string name="chat_attach">Attach file</string>
|
<string name="chat_attach">Attach file</string>
|
||||||
<string name="chat_record_audio">Record audio</string>
|
<string name="chat_record_audio">Record audio</string>
|
||||||
<string name="chat_stop_recording">Stop recording</string>
|
|
||||||
<string name="chat_send_audio">Send audio</string>
|
<string name="chat_send_audio">Send audio</string>
|
||||||
<string name="chat_cancel_audio">Cancel audio</string>
|
<string name="chat_cancel_audio">Cancel audio</string>
|
||||||
<string name="chat_encryption_none">No encryption</string>
|
|
||||||
<string name="chat_encryption_otr">OTR</string>
|
|
||||||
<string name="chat_encryption_omemo">OMEMO</string>
|
|
||||||
<string name="chat_encryption_pgp">OpenPGP</string>
|
|
||||||
<string name="chat_encryption_select">Select encryption</string>
|
<string name="chat_encryption_select">Select encryption</string>
|
||||||
<string name="chat_message_delivered">Delivered</string>
|
<string name="chat_message_delivered">Delivered</string>
|
||||||
<string name="chat_message_read">Read</string>
|
<string name="chat_message_read">Read</string>
|
||||||
@@ -75,17 +53,9 @@
|
|||||||
<string name="chat_message_failed">Failed to send</string>
|
<string name="chat_message_failed">Failed to send</string>
|
||||||
<string name="chat_typing">%1$s is typing…</string>
|
<string name="chat_typing">%1$s is typing…</string>
|
||||||
<string name="chat_media_image">Image</string>
|
<string name="chat_media_image">Image</string>
|
||||||
<string name="chat_media_video">Video</string>
|
|
||||||
<string name="chat_media_audio">Audio</string>
|
<string name="chat_media_audio">Audio</string>
|
||||||
<string name="chat_media_file">File</string>
|
<string name="chat_media_file">File</string>
|
||||||
<string name="chat_media_uploading">Uploading…</string>
|
|
||||||
<string name="chat_media_download">Download</string>
|
|
||||||
<string name="chat_empty">No messages yet.\nSay hello!</string>
|
<string name="chat_empty">No messages yet.\nSay hello!</string>
|
||||||
<string name="chat_otr_started">OTR session started. Your conversation is now encrypted.</string>
|
|
||||||
<string name="chat_otr_ended">OTR session ended.</string>
|
|
||||||
<string name="chat_otr_untrusted">Warning: OTR fingerprint not verified.</string>
|
|
||||||
<string name="chat_omemo_trusted">OMEMO: All devices trusted.</string>
|
|
||||||
<string name="chat_omemo_untrusted">OMEMO: Untrusted devices detected.</string>
|
|
||||||
|
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<string name="settings_title">Settings</string>
|
<string name="settings_title">Settings</string>
|
||||||
@@ -95,62 +65,31 @@
|
|||||||
<string name="settings_theme_light">Light</string>
|
<string name="settings_theme_light">Light</string>
|
||||||
<string name="settings_theme_dark">Dark</string>
|
<string name="settings_theme_dark">Dark</string>
|
||||||
<string name="settings_language">Language</string>
|
<string name="settings_language">Language</string>
|
||||||
<string name="settings_language_en">English</string>
|
|
||||||
<string name="settings_language_es">Español</string>
|
|
||||||
<string name="settings_language_zh">中文</string>
|
|
||||||
<string name="settings_notifications">Notifications</string>
|
<string name="settings_notifications">Notifications</string>
|
||||||
<string name="settings_notifications_messages">Message notifications</string>
|
<string name="settings_notifications_messages">Message notifications</string>
|
||||||
<string name="settings_notifications_vibrate">Vibrate</string>
|
<string name="settings_notifications_vibrate">Vibrate</string>
|
||||||
<string name="settings_notifications_sound">Sound</string>
|
<string name="settings_notifications_sound">Sound</string>
|
||||||
<string name="settings_encryption">Encryption</string>
|
<string name="settings_encryption">Encryption</string>
|
||||||
<string name="settings_omemo_devices">OMEMO Devices</string>
|
|
||||||
<string name="settings_pgp_keys">OpenPGP Keys</string>
|
|
||||||
<string name="settings_otr_fingerprints">OTR Fingerprints</string>
|
|
||||||
<string name="settings_default_encryption">Default encryption</string>
|
<string name="settings_default_encryption">Default encryption</string>
|
||||||
<string name="settings_about">About</string>
|
<string name="settings_about">About</string>
|
||||||
<string name="settings_version">Version</string>
|
<string name="settings_version">Version</string>
|
||||||
|
|
||||||
<!-- Common -->
|
<!-- Common -->
|
||||||
<string name="ok">OK</string>
|
|
||||||
<string name="cancel">Cancel</string>
|
<string name="cancel">Cancel</string>
|
||||||
<string name="save">Save</string>
|
<string name="save">Save</string>
|
||||||
<string name="delete">Delete</string>
|
<string name="delete">Delete</string>
|
||||||
<string name="confirm">Confirm</string>
|
|
||||||
<string name="error">Error</string>
|
|
||||||
<string name="loading">Loading…</string>
|
|
||||||
<string name="retry">Retry</string>
|
|
||||||
<string name="close">Close</string>
|
<string name="close">Close</string>
|
||||||
<string name="search">Search</string>
|
|
||||||
<string name="clear">Clear</string>
|
|
||||||
<string name="back">Back</string>
|
<string name="back">Back</string>
|
||||||
<string name="more_options">More options</string>
|
<string name="more_options">More options</string>
|
||||||
|
|
||||||
<!-- Permissions -->
|
|
||||||
<string name="permission_microphone_title">Microphone Permission</string>
|
|
||||||
<string name="permission_microphone_message">AleJabber needs microphone access to record audio messages.</string>
|
|
||||||
<string name="permission_storage_title">Storage Permission</string>
|
|
||||||
<string name="permission_storage_message">AleJabber needs storage access to send and receive files.</string>
|
|
||||||
<string name="permission_camera_title">Camera Permission</string>
|
|
||||||
<string name="permission_camera_message">AleJabber needs camera access to take photos.</string>
|
|
||||||
<string name="permission_denied">Permission denied. Please grant it in Settings.</string>
|
|
||||||
<string name="open_settings">Open Settings</string>
|
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<string name="notification_channel_messages">Messages</string>
|
<string name="notification_channel_messages">Messages</string>
|
||||||
<string name="notification_channel_messages_desc">Incoming chat messages</string>
|
<string name="notification_channel_messages_desc">Incoming chat messages</string>
|
||||||
<string name="notification_channel_service">XMPP Service</string>
|
<string name="notification_channel_service">XMPP Service</string>
|
||||||
<string name="notification_channel_service_desc">Background XMPP connection</string>
|
<string name="notification_channel_service_desc">Background XMPP connection</string>
|
||||||
<string name="notification_service_running">AleJabber is connected</string>
|
<string name="notification_service_running">AleJabber is connected</string>
|
||||||
<string name="notification_new_message">New message from %1$s</string>
|
|
||||||
|
|
||||||
<!-- Accessibility -->
|
<!-- Accessibility -->
|
||||||
<string name="cd_avatar">%1$s\'s avatar</string>
|
<string name="cd_avatar">%1$s\'s avatar</string>
|
||||||
<string name="cd_status_indicator">Status: %1$s</string>
|
|
||||||
<string name="cd_encryption_badge">Encryption: %1$s</string>
|
<string name="cd_encryption_badge">Encryption: %1$s</string>
|
||||||
<string name="cd_send_button">Send message</string>
|
|
||||||
<string name="cd_attach_button">Attach file</string>
|
|
||||||
<string name="cd_record_button">Record audio message</string>
|
|
||||||
<string name="cd_message_delivered">Message delivered</string>
|
|
||||||
<string name="cd_message_read">Message read</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ junitVersion = "1.3.0"
|
|||||||
espressoCore = "3.7.0"
|
espressoCore = "3.7.0"
|
||||||
lifecycleRuntimeKtx = "2.10.0"
|
lifecycleRuntimeKtx = "2.10.0"
|
||||||
activityCompose = "1.12.4"
|
activityCompose = "1.12.4"
|
||||||
kotlin = "2.0.21"
|
kotlin = "2.1.21"
|
||||||
composeBom = "2024.09.00"
|
composeBom = "2024.09.00"
|
||||||
hilt = "2.59.2"
|
hilt = "2.59.2"
|
||||||
room = "2.7.1"
|
room = "2.7.1"
|
||||||
@@ -15,11 +15,11 @@ datastore = "1.1.7"
|
|||||||
coil = "2.7.0"
|
coil = "2.7.0"
|
||||||
smack = "4.4.8"
|
smack = "4.4.8"
|
||||||
okhttp = "4.12.0"
|
okhttp = "4.12.0"
|
||||||
coroutines = "1.9.0"
|
coroutines = "1.10.2"
|
||||||
viewmodelCompose = "2.10.0"
|
viewmodelCompose = "2.10.0"
|
||||||
materialIconsExtended = "1.7.8"
|
materialIconsExtended = "1.7.8"
|
||||||
bouncycastle = "1.78.1"
|
bouncycastle = "1.83"
|
||||||
accompanist = "0.36.0"
|
accompanist = "0.37.3"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
|||||||
Referencia en una nueva incidencia
Block a user