Compare commits

..
17 Commits
Author SHA1 Message Date
Vitor Pamplona 308a79a305 0.22.3 2023-03-03 19:43:44 -05:00
Vitor Pamplona 5e5c393472 Bringing back the idea of checking for change before updating the screen. The check happens between 500us and 1ms and the screen update generally in 10-100ms 2023-03-03 19:40:26 -05:00
Vitor Pamplona ea905ef6ed Removes the element that was forcing everything to be right aligned 2023-03-03 18:14:51 -05:00
Vitor Pamplona 5f98a54452 Reverting change to activate RTL given some characters, it was picking up some english chars 2023-03-03 18:11:50 -05:00
Vitor Pamplona 8627fd4b4c Delete Origin Header (snort doesn't need it anymore) 2023-03-03 18:08:09 -05:00
Vitor PamplonaandGitHub 2a65b7241a Merge pull request #186 from rashedswen/main
temporary solution for mirroring arabic text
2023-03-03 18:06:46 -05:00
Vitor PamplonaandGitHub 7863961aba Merge branch 'main' into main 2023-03-03 18:06:40 -05:00
Vitor PamplonaandGitHub a84be7fc61 Merge pull request #187 from Chemaclass/refactor-Nip19
Refactor Nip19
2023-03-03 18:03:58 -05:00
Chemaclass 657f99a65a Remove non-used imports in Nip19 2023-03-03 23:40:09 +01:00
Chemaclass 91591abd14 Extract method refactoring in Nip19::uriToRoute() 2023-03-03 23:36:16 +01:00
Chemaclass fd58da2a93 Remove 1 indentation level from uriToRoute() 2023-03-03 23:26:35 +01:00
Chemaclass b35a59372c Prepare move unit tests for uri_to_route behaviour 2023-03-03 23:23:42 +01:00
Chemaclass c1113f9df9 Add test uri_to_route_npub 2023-03-03 23:19:05 +01:00
Chemaclass bd3d7e1aa3 Prepare test parse_TLV 2023-03-03 22:58:24 +01:00
Chemaclass 47f3fe5cc6 refactor Nip19Test introduce byteArrayOfInts() 2023-03-03 22:46:15 +01:00
Chemaclass b6e16ad470 Create Nip19Test testing toInt32() 2023-03-03 22:40:46 +01:00
Rashed fe80b509e3 temporary solution for mirroring arabic text 2023-03-03 23:44:39 +03:00
17 changed files with 227 additions and 59 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId "com.vitorpamplona.amethyst"
minSdk 26
targetSdk 33
versionCode 85
versionName "0.22.2"
versionCode 86
versionName "0.22.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -1,71 +1,103 @@
package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.toByteArray
import com.vitorpamplona.amethyst.model.toHexKey
import com.vitorpamplona.amethyst.service.model.ATag
import nostr.postr.bechToBytes
import java.nio.ByteBuffer
import java.nio.ByteOrder
import nostr.postr.Bech32
import nostr.postr.bechToBytes
import nostr.postr.toByteArray
class Nip19 {
enum class Type {
USER, NOTE, RELAY, ADDRESS
}
data class Return(val type: Type, val hex: String)
fun uriToRoute(uri: String?): Return? {
try {
val key = uri?.removePrefix("nostr:")
val key = uri?.removePrefix("nostr:") ?: return null
if (key != null) {
val bytes = key.bechToBytes()
if (key.startsWith("npub")) {
return Return(Type.USER, bytes.toHexKey())
}
if (key.startsWith("note")) {
return Return(Type.NOTE, bytes.toHexKey())
}
if (key.startsWith("nprofile")) {
val tlv = parseTLV(bytes)
val hex = tlv.get(NIP19TLVTypes.SPECIAL.id)?.get(0)?.toHexKey()
if (hex != null)
return Return(Type.USER, hex)
}
if (key.startsWith("nevent")) {
val tlv = parseTLV(bytes)
val hex = tlv.get(NIP19TLVTypes.SPECIAL.id)?.get(0)?.toHexKey()
if (hex != null)
return Return(Type.USER, hex)
}
if (key.startsWith("nrelay")) {
val tlv = parseTLV(bytes)
val relayUrl = tlv.get(NIP19TLVTypes.SPECIAL.id)?.get(0)?.toString(Charsets.UTF_8)
if (relayUrl != null)
return Return(Type.RELAY, relayUrl)
}
if (key.startsWith("naddr")) {
val tlv = parseTLV(bytes)
val d = tlv.get(NIP19TLVTypes.SPECIAL.id)?.get(0)?.toString(Charsets.UTF_8)
val relay = tlv.get(NIP19TLVTypes.RELAY.id)?.get(0)?.toString(Charsets.UTF_8)
val author = tlv.get(NIP19TLVTypes.AUTHOR.id)?.get(0)?.toHexKey()
val kind = tlv.get(NIP19TLVTypes.KIND.id)?.get(0)?.let { toInt32(it) }
if (d != null)
return Return(Type.ADDRESS, "$kind:$author:$d")
}
val bytes = key.bechToBytes()
if (key.startsWith("npub")) {
return npub(bytes)
} else if (key.startsWith("note")) {
return note(bytes)
} else if (key.startsWith("nprofile")) {
return nprofile(bytes)
} else if (key.startsWith("nevent")) {
return nevent(bytes)
} else if (key.startsWith("nrelay")) {
return nrelay(bytes)
} else if (key.startsWith("naddr")) {
return naddr(bytes)
}
} catch (e: Throwable) {
println("Issue trying to Decode NIP19 ${uri}: ${e.message}")
//e.printStackTrace()
}
return null
}
private fun npub(bytes: ByteArray): Return {
return Return(Type.USER, bytes.toHexKey())
}
private fun note(bytes: ByteArray): Return {
return Return(Type.NOTE, bytes.toHexKey());
}
private fun nprofile(bytes: ByteArray): Return? {
val hex = parseTLV(bytes)
.get(NIP19TLVTypes.SPECIAL.id)
?.get(0)
?.toHexKey() ?: return null
return Return(Type.USER, hex)
}
private fun nevent(bytes: ByteArray): Return? {
val hex = parseTLV(bytes)
.get(NIP19TLVTypes.SPECIAL.id)
?.get(0)
?.toHexKey() ?: return null
return Return(Type.USER, hex)
}
private fun nrelay(bytes: ByteArray): Return? {
val relayUrl = parseTLV(bytes)
.get(NIP19TLVTypes.SPECIAL.id)
?.get(0)
?.toString(Charsets.UTF_8) ?: return null
return Return(Type.RELAY, relayUrl)
}
private fun naddr(bytes: ByteArray): Return? {
val tlv = parseTLV(bytes)
val d = tlv.get(NIP19TLVTypes.SPECIAL.id)
?.get(0)
?.toString(Charsets.UTF_8) ?: return null
val relay = tlv.get(NIP19TLVTypes.RELAY.id)
?.get(0)
?.toString(Charsets.UTF_8)
val author = tlv.get(NIP19TLVTypes.AUTHOR.id)
?.get(0)
?.toHexKey()
val kind = tlv.get(NIP19TLVTypes.KIND.id)
?.get(0)
?.let { toInt32(it) }
return Return(Type.ADDRESS, "$kind:$author:$d")
}
}
enum class NIP19TLVTypes(val id: Byte) { //classes should start with an uppercase letter in kotlin
// Classes should start with an uppercase letter in kotlin
enum class NIP19TLVTypes(val id: Byte) {
SPECIAL(0),
RELAY(1),
AUTHOR(2),
@@ -78,19 +110,19 @@ fun toInt32(bytes: ByteArray): Int {
}
fun parseTLV(data: ByteArray): Map<Byte, List<ByteArray>> {
var result = mutableMapOf<Byte, MutableList<ByteArray>>()
val result = mutableMapOf<Byte, MutableList<ByteArray>>()
var rest = data
while (rest.isNotEmpty()) {
val t = rest[0]
val l = rest[1]
val v = rest.sliceArray(IntRange(2, (2 + l) - 1))
rest = rest.sliceArray(IntRange(2 + l, rest.size-1))
rest = rest.sliceArray(IntRange(2 + l, rest.size - 1))
if (v.size < l) continue
if (!result.containsKey(t)) {
result.put(t, mutableListOf())
result[t] = mutableListOf()
}
result.get(t)?.add(v)
result[t]?.add(v)
}
return result
}
@@ -54,7 +54,7 @@ class Relay(
if (socket != null) return
try {
val request = Request.Builder().header("Origin", "amethyst.social").url(url.trim()).build()
val request = Request.Builder().url(url.trim()).build()
val listener = object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
@@ -13,6 +13,7 @@ import androidx.compose.material.ButtonDefaults
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -22,11 +23,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.platform.LocalLayoutDirection
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
import androidx.compose.ui.unit.LayoutDirection
@Composable
fun ExpandableRichTextViewer(
@@ -43,7 +46,17 @@ fun ExpandableRichTextViewer(
val text = if (showFullText) content else content.take(350)
Box(contentAlignment = Alignment.BottomCenter) {
RichTextViewer(text, canPreview, modifier, tags, backgroundColor, accountViewModel, navController)
//CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
RichTextViewer(
text,
canPreview,
modifier,
tags,
backgroundColor,
accountViewModel,
navController
)
//}
if (content.length > 350 && !showFullText) {
Row(
@@ -29,6 +29,7 @@ import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.dp
@@ -132,8 +133,8 @@ fun RichTextViewer(
// FlowRow doesn't work well with paragraphs. So we need to split them
content.split('\n').forEach { paragraph ->
FlowRow() {
paragraph.split(' ').forEach { word: String ->
val s = if (isArabic(paragraph)) paragraph.split(' ').reversed() else paragraph.split(' ');
s.forEach { word: String ->
if (canPreview) {
// Explicit URL
val lnInvoice = LnInvoiceUtil.findInvoice(word)
@@ -191,6 +192,10 @@ fun RichTextViewer(
}
}
private fun isArabic(text: String): Boolean {
return text.any { it in '\u0600'..'\u06FF' || it in '\u0750'..'\u077F' }
}
fun isBechLink(word: String): Boolean {
return word.startsWith("nostr:", true)
@@ -183,7 +183,6 @@ fun TranslateableRichTextViewer(
Spacer(modifier = Modifier.size(10.dp))
// TODO : Rashed translate this
Text(
"${stringResource(R.string.show_in)} ${Locale(source).displayName} ${
stringResource(
@@ -241,7 +241,7 @@ fun NoteCompose(
if (noteEvent is RepostEvent) {
Text(
" boosted",
" ${stringResource(id = R.string.boosted)}",
fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
@@ -63,9 +63,9 @@ abstract class FeedViewModel(val localFilter: FeedFilter<Note>): ViewModel() {
val oldNotesState = feedContent.value
if (oldNotesState is FeedState.Loaded) {
// Using size as a proxy for has changed.
//if (notes != oldNotesState.feed.value) {
if (notes != oldNotesState.feed.value) {
updateFeed(notes)
//}
}
} else {
updateFeed(notes)
}
@@ -38,7 +38,9 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter<Pair<Note, Note>>): Vie
val oldNotesState = feedContent.value
if (oldNotesState is LnZapFeedState.Loaded) {
// Using size as a proxy for has changed.
updateFeed(notes)
if (notes != oldNotesState.feed.value) {
updateFeed(notes)
}
} else {
updateFeed(notes)
}
@@ -42,7 +42,9 @@ open class UserFeedViewModel(val dataSource: FeedFilter<User>): ViewModel() {
val oldNotesState = feedContent.value
if (oldNotesState is UserFeedState.Loaded) {
// Using size as a proxy for has changed.
updateFeed(notes)
if (notes != oldNotesState.feed.value) {
updateFeed(notes)
}
} else {
updateFeed(notes)
}
+1
View File
@@ -35,6 +35,7 @@
<string name="zaps">Zaps</string>
<string name="view_count">مشاهدة العد</string>
<string name="boost">تعزيز</string>
<string name="boosted">معزز</string>
<string name="quote">إقتباس</string>
<string name="new_amount_in_sats">مبلغ جديد في Sats</string>
<string name="add">إضافة</string>
+1
View File
@@ -37,6 +37,7 @@
<string name="zaps">Zaps</string>
<string name="view_count">Total vistas</string>
<string name="boost">Impulsar</string>
<string name="boosted">boosted</string>
<string name="quote">Cita</string>
<string name="new_amount_in_sats">Nueva cantidad en Sats</string>
<string name="add">Añadir</string>
@@ -35,6 +35,7 @@
<string name="zaps">Zaps</string>
<string name="view_count">Contagem de visualizações</string>
<string name="boost">Impulsionar</string>
<string name="boosted">boosted</string>
<string name="quote">Citar</string>
<string name="new_amount_in_sats">Novo Valor em Sats</string>
<string name="add">Adicionar</string>
+1
View File
@@ -36,6 +36,7 @@
<string name="zaps">Запы</string>
<string name="view_count">Просмотры</string>
<string name="boost">Продвинуть</string>
<string name="boosted">boosted</string>
<string name="quote">Цитировать</string>
<string name="new_amount_in_sats">Новая сумма в sat</string>
<string name="add">Добавить</string>
+1
View File
@@ -36,6 +36,7 @@
<string name="zaps">Запи</string>
<string name="view_count">Перегляди</string>
<string name="boost">Просувати</string>
<string name="boosted">boosted</string>
<string name="quote">Цитувати</string>
<string name="new_amount_in_sats">Нова сума в sat</string>
<string name="add">Додати</string>
+1
View File
@@ -36,6 +36,7 @@
<string name="zaps">Zaps</string>
<string name="view_count">View count</string>
<string name="boost">Boost</string>
<string name="boosted">boosted</string>
<string name="quote">Quote</string>
<string name="new_amount_in_sats">New Amount in Sats</string>
<string name="add">Add</string>
@@ -0,0 +1,109 @@
package com.vitorpamplona.amethyst.service
import org.junit.Assert
import org.junit.Ignore
import org.junit.Test
class Nip19Test {
private val nip19 = Nip19();
@Test(expected = IllegalArgumentException::class)
fun to_int_32_length_smaller_than_4() {
toInt32(byteArrayOfInts(1, 2, 3))
}
@Test(expected = IllegalArgumentException::class)
fun to_int_32_length_bigger_than_4() {
toInt32(byteArrayOfInts(1, 2, 3, 4, 5))
}
@Test()
fun to_int_32_length_4() {
val actual = toInt32(byteArrayOfInts(1, 2, 3, 4))
Assert.assertEquals(16909060, actual)
}
@Ignore("Test not implemented yet")
@Test()
fun parse_TLV() {
// TODO: I don't know how to test this (?)
}
@Test()
fun uri_to_route_null() {
val actual = nip19.uriToRoute(null)
Assert.assertEquals(null, actual)
}
@Test()
fun uri_to_route_unknown() {
val actual = nip19.uriToRoute("nostr:unknown")
Assert.assertEquals(null, actual)
}
@Test()
fun uri_to_route_npub() {
val actual =
nip19.uriToRoute("nostr:npub1hv7k2s755n697sptva8vkh9jz40lzfzklnwj6ekewfmxp5crwdjs27007y")
Assert.assertEquals(Nip19.Type.USER, actual?.type)
Assert.assertEquals(
"bb3d6543d4a4f45f402b674ecb5cb2155ff12456fcdd2d66d9727660d3037365",
actual?.hex
)
}
@Test()
fun uri_to_route_note() {
val actual =
nip19.uriToRoute("nostr:note1stqea6wmwezg9x6yyr6qkukw95ewtdukyaztycws65l8wppjmtpscawevv")
Assert.assertEquals(Nip19.Type.NOTE, actual?.type)
Assert.assertEquals(
"82c19ee9db7644829b4420f40b72ce2d32e5b7962744b261d0d53e770432dac3",
actual?.hex
)
}
@Ignore("Test not implemented yet")
@Test()
fun uri_to_route_nprofile() {
val actual = nip19.uriToRoute("nostr:nprofile")
Assert.assertEquals(Nip19.Type.USER, actual?.type)
Assert.assertEquals("*", actual?.hex)
}
@Ignore("Test not implemented yet")
@Test()
fun uri_to_route_nevent() {
val actual = nip19.uriToRoute("nostr:nevent")
Assert.assertEquals(Nip19.Type.USER, actual?.type)
Assert.assertEquals("*", actual?.hex)
}
@Ignore("Test not implemented yet")
@Test()
fun uri_to_route_nrelay() {
val actual = nip19.uriToRoute("nostr:nrelay")
Assert.assertEquals(Nip19.Type.RELAY, actual?.type)
Assert.assertEquals("*", actual?.hex)
}
@Ignore("Test not implemented yet")
@Test()
fun uri_to_route_naddr() {
val actual = nip19.uriToRoute("nostr:naddr")
Assert.assertEquals(Nip19.Type.ADDRESS, actual?.type)
Assert.assertEquals("*", actual?.hex)
}
private fun byteArrayOfInts(vararg ints: Int) = ByteArray(ints.size) { pos -> ints[pos].toByte() }
}