diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 9b72e8e47..2093d0bb1 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -42,15 +42,30 @@ jobs:
- name: Build APK (gradle)
run: ./gradlew assembleDebug --no-daemon
- - name: Upload APK
+ - name: Upload Play APK
uses: actions/upload-artifact@v4
with:
- name: Debug APK
- path: amethyst/build/outputs/apk/debug/app-debug.apk
+ name: Play Debug APK
+ path: amethyst/build/outputs/apk/play/debug/amethyst-play-universal-debug.apk
+
+ - name: Upload FDroid APK
+ uses: actions/upload-artifact@v4
+ with:
+ name: FDroid Debug APK
+ path: amethyst/build/outputs/apk/fdroid/debug/amethyst-fdroid-universal-debug.apk
+
+ - name: Upload Compose Reports
+ uses: actions/upload-artifact@v4
+ with:
+ name: Compose Reports
+ path: amethyst/build/compose_compiler
+
+ - name: Tests
+ run: ./gradlew test --no-daemon
- name: Upload Test Results
uses: actions/upload-artifact@v4
with:
- name: Build Reports
+ name: Test Reports
path: amethyst/build/reports
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index d4b7accba..bb4493707 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 8e59b99b1..851ee8374 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,9 @@ Join the social network you control.
## Download and Install
+[
](https://github.com/zapstore/zapstore/releases)
[
](https://github.com/ImranR98/Obtainium)
diff --git a/amethyst/build.gradle b/amethyst/build.gradle
index 40a9e17b4..20bc1e955 100644
--- a/amethyst/build.gradle
+++ b/amethyst/build.gradle
@@ -16,9 +16,9 @@ android {
applicationId "com.vitorpamplona.amethyst"
minSdk libs.versions.android.minSdk.get().toInteger()
targetSdk libs.versions.android.targetSdk.get().toInteger()
- versionCode 412
- versionName "0.92.7"
- buildConfigField "String", "RELEASE_NOTES_ID", "\"98f2acaf9b3eb70d87607942e576f9a086ac28adf37869da9a29f4815cecac86\""
+ versionCode 414
+ versionName "0.93.1"
+ buildConfigField "String", "RELEASE_NOTES_ID", "\"f9e228d0579b0256044b54a78037f06edfa06c24862c368ad7e02b5caa7bd309\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -233,9 +233,6 @@ dependencies {
// enables network for coil
implementation libs.coil.okhttp
- // create blurhash
- implementation libs.trbl.blurhash
-
// Permission to upload pictures:
implementation libs.accompanist.permissions
diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
index 856518bec..495c1ed7a 100644
--- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
+++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
@@ -26,12 +26,17 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AccountSettings
-import com.vitorpamplona.amethyst.service.FileHeader
-import com.vitorpamplona.amethyst.service.Nip96MediaServers
-import com.vitorpamplona.amethyst.service.Nip96Retriever
-import com.vitorpamplona.amethyst.service.Nip96Uploader
-import com.vitorpamplona.amethyst.ui.actions.ImageDownloader
+import com.vitorpamplona.amethyst.service.uploads.FileHeader
+import com.vitorpamplona.amethyst.service.uploads.ImageDownloader
+import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
+import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader
+import com.vitorpamplona.amethyst.service.uploads.nip96.ServerInfoRetriever
+import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
+import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
+import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
+import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.crypto.KeyPair
+import com.vitorpamplona.quartz.encoders.toHexKey
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.fail
import kotlinx.coroutines.CoroutineScope
@@ -47,63 +52,98 @@ import kotlin.random.Random
@RunWith(AndroidJUnit4::class)
class ImageUploadTesting {
- private suspend fun testBase(server: Nip96MediaServers.ServerName) {
- val serverInfo =
- Nip96Retriever()
- .loadInfo(
- server.baseUrl,
- false,
- )
+ val account =
+ Account(
+ AccountSettings(KeyPair()),
+ scope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
+ )
+ private suspend fun getBitmap(): ByteArray {
val bitmap = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888)
for (x in 0 until bitmap.width) {
for (y in 0 until bitmap.height) {
bitmap.setPixel(x, y, Color.rgb(Random.nextInt(), Random.nextInt(), Random.nextInt()))
}
}
+
val baos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos)
- val bytes = baos.toByteArray()
- val inputStream = bytes.inputStream()
+ return baos.toByteArray()
+ }
- val account =
- Account(
- AccountSettings(KeyPair()),
- scope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
- )
+ private suspend fun testBase(server: ServerName) {
+ if (server.type == ServerType.NIP96) {
+ testNip96(server)
+ } else {
+ testBlossom(server)
+ }
+ }
+ private suspend fun testBlossom(server: ServerName) {
+ val paylod = getBitmap()
+ val initialHash = CryptoUtils.sha256(paylod).toHexKey()
+ val inputStream = paylod.inputStream()
val result =
- Nip96Uploader(account)
+ BlossomUploader()
.uploadImage(
- inputStream,
- bytes.size.toLong(),
- "image/png",
+ inputStream = inputStream,
+ hash = initialHash,
+ length = paylod.size,
+ baseFileName = "filename.png",
+ contentType = "image/png",
alt = null,
sensitiveContent = null,
- serverInfo,
+ serverBaseUrl = server.baseUrl,
forceProxy = { false },
- onProgress = {},
+ httpAuth = account::createBlossomUploadAuth,
context = InstrumentationRegistry.getInstrumentation().targetContext,
)
- val url = result.tags!!.first { it[0] == "url" }.get(1)
- val size =
- result.tags!!
- .firstOrNull { it[0] == "size" }
- ?.get(1)
- ?.ifBlank { null }
- val dim =
- result.tags!!
- .firstOrNull { it[0] == "dim" }
- ?.get(1)
- ?.ifBlank { null }
- val hash =
- result.tags!!
- .firstOrNull { it[0] == "x" }
- ?.get(1)
- ?.ifBlank { null }
- val contentType = result.tags!!.first { it[0] == "m" }.get(1)
- val ox = result.tags!!.first { it[0] == "ox" }.get(1)
+ assertEquals("image/png", result.type)
+ assertEquals(paylod.size.toLong(), result.size)
+ assertEquals(initialHash, result.sha256)
+ assertEquals("${server.baseUrl}/$initialHash", result.url?.removeSuffix(".png"))
+
+ val imageData: ByteArray =
+ ImageDownloader().waitAndGetImage(result.url!!, false)
+ ?: run {
+ fail("${server.name}: Should not be null")
+ return
+ }
+
+ val downloadedHash = CryptoUtils.sha256(imageData).toHexKey()
+ assertEquals(initialHash, downloadedHash)
+ }
+
+ private suspend fun testNip96(server: ServerName) {
+ val serverInfo =
+ ServerInfoRetriever()
+ .loadInfo(
+ server.baseUrl,
+ false,
+ )
+
+ val paylod = getBitmap()
+ val inputStream = paylod.inputStream()
+ val result =
+ Nip96Uploader()
+ .uploadImage(
+ inputStream = inputStream,
+ length = paylod.size.toLong(),
+ contentType = "image/png",
+ alt = null,
+ sensitiveContent = null,
+ server = serverInfo,
+ forceProxy = { false },
+ onProgress = {},
+ httpAuth = account::createHTTPAuthorization,
+ context = InstrumentationRegistry.getInstrumentation().targetContext,
+ )
+
+ val url = result.url!!
+ val size = result.size
+ val dim = result.dimension
+ val hash = result.sha256
Assert.assertTrue("${server.name}: Invalid result url", url.startsWith("http"))
@@ -114,22 +154,23 @@ class ImageUploadTesting {
return
}
- FileHeader.prepare(
- imageData,
- "image/png",
- null,
- onReady = {
+ val prepared =
+ FileHeader.prepare(
+ imageData,
+ "image/png",
+ null,
+ )
+
+ prepared.fold(
+ onSuccess = {
if (dim != null) {
- // assertEquals("${server.name}: Invalid dimensions", it.dim, dim)
+ assertEquals("${server.name}: Invalid dimensions", it.dim.toString(), dim.toString())
}
if (size != null) {
- // assertEquals("${server.name}: Invalid size", it.size.toString(), size)
- }
- if (hash != null) {
- assertEquals("${server.name}: Invalid hash", it.hash, hash)
+ assertEquals("${server.name}: Invalid size", it.size.toString(), size)
}
},
- onError = { fail("${server.name}: It should not fail") },
+ onFailure = { fail("${server.name}: It should not fail") },
)
// delay(1000)
@@ -140,66 +181,88 @@ class ImageUploadTesting {
@Test
fun runTestOnDefaultServers() =
runBlocking {
- Nip96MediaServers.DEFAULT.forEach {
- testBase(it)
+ DEFAULT_MEDIA_SERVERS.forEach {
+ // skip paid servers and primal server is buggy.
+ if (!it.name.contains("Paid") && !it.name.contains("Primal")) {
+ testBase(it)
+ }
}
}
@Test()
fun testNostrCheck() =
runBlocking {
- testBase(Nip96MediaServers.ServerName("nostrcheck.me", "https://nostrcheck.me"))
+ testBase(ServerName("nostrcheck.me", "https://nostrcheck.me", ServerType.NIP96))
}
@Test()
@Ignore("Not Working anymore")
fun testNostrage() =
runBlocking {
- testBase(Nip96MediaServers.ServerName("nostrage", "https://nostrage.com"))
+ testBase(ServerName("nostrage", "https://nostrage.com", ServerType.NIP96))
}
@Test()
@Ignore("Not Working anymore")
fun testSove() =
runBlocking {
- testBase(Nip96MediaServers.ServerName("sove", "https://sove.rent"))
+ testBase(ServerName("sove", "https://sove.rent", ServerType.NIP96))
}
@Test()
fun testNostrBuild() =
runBlocking {
- testBase(Nip96MediaServers.ServerName("nostr.build", "https://nostr.build"))
+ testBase(ServerName("nostr.build", "https://nostr.build", ServerType.NIP96))
}
@Test()
@Ignore("Not Working anymore")
fun testSovbit() =
runBlocking {
- testBase(Nip96MediaServers.ServerName("sovbit", "https://files.sovbit.host"))
+ testBase(ServerName("sovbit", "https://files.sovbit.host", ServerType.NIP96))
}
@Test()
fun testVoidCat() =
runBlocking {
- testBase(Nip96MediaServers.ServerName("void.cat", "https://void.cat"))
+ testBase(ServerName("void.cat", "https://void.cat", ServerType.NIP96))
}
@Test()
+ @Ignore("Returns invalid image size")
fun testNostrPic() =
runBlocking {
- testBase(Nip96MediaServers.ServerName("nostpic.com", "https://nostpic.com"))
+ testBase(ServerName("nostpic.com", "https://nostpic.com", ServerType.NIP96))
}
@Test(expected = RuntimeException::class)
fun testSprovoostNl() =
runBlocking {
- testBase(Nip96MediaServers.ServerName("sprovoost.nl", "https://img.sprovoost.nl/"))
+ testBase(ServerName("sprovoost.nl", "https://img.sprovoost.nl/", ServerType.NIP96))
}
@Test()
@Ignore("Not Working anymore")
fun testNostrOnch() =
runBlocking {
- testBase(Nip96MediaServers.ServerName("nostr.onch.services", "https://nostr.onch.services"))
+ testBase(ServerName("nostr.onch.services", "https://nostr.onch.services", ServerType.NIP96))
+ }
+
+ @Ignore("Changes sha256")
+ fun testPrimalBlossom() =
+ runBlocking {
+ testBase(ServerName("primal.net", "https://blossom.primal.net", ServerType.Blossom))
+ }
+
+ @Test()
+ fun testNostrCheckBlossom() =
+ runBlocking {
+ testBase(ServerName("nostrcheck", "https://cdn.nostrcheck.me", ServerType.Blossom))
+ }
+
+ @Ignore("Requires Payment")
+ fun testSatelliteBlossom() =
+ runBlocking {
+ testBase(ServerName("satellite", "https://cdn.satellite.earth", ServerType.Blossom))
}
}
diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/URIParserTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/URIParserTest.kt
new file mode 100644
index 000000000..a8c751dbd
--- /dev/null
+++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/URIParserTest.kt
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.amethyst
+
+import com.vitorpamplona.amethyst.ui.navigation.findParameterValue
+import junit.framework.TestCase.assertEquals
+import org.junit.Test
+import java.net.URI
+
+class URIParserTest {
+ @Test
+ fun testNEventWithAccount() {
+ val test = "nevent1qqsp9wg5r3vkuv5al0459h3h44yxh6r0y7chgvu3pkdxhcj99t3msrgpzdmhxue69uhhwmm59e6hg7r09ehkuef0qgsdhcrqt2w8x9et446j8ge8kgmd2h4ykc6wsrnc4yqnmdu3lr74ktqrqsqqqqqp578kku?account=npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z"
+ val testUri = URI(test)
+
+ // assertEquals("nostr", testUri.scheme)
+ assertEquals(null, testUri.authority)
+ assertEquals(null, testUri.host)
+ assertEquals(-1, testUri.port)
+ assertEquals(null, testUri.userInfo)
+ assertEquals("nevent1qqsp9wg5r3vkuv5al0459h3h44yxh6r0y7chgvu3pkdxhcj99t3msrgpzdmhxue69uhhwmm59e6hg7r09ehkuef0qgsdhcrqt2w8x9et446j8ge8kgmd2h4ykc6wsrnc4yqnmdu3lr74ktqrqsqqqqqp578kku", testUri.path)
+ assertEquals(null, testUri.fragment)
+ assertEquals("account=npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z", testUri.rawQuery)
+
+ assertEquals("npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z", testUri.findParameterValue("account"))
+ }
+
+ @Test
+ fun testNotificationsWithAccount() {
+ val test = "notifications?account=npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z"
+ val testUri = URI(test)
+
+ // assertEquals("nostr", testUri.scheme)
+ assertEquals(null, testUri.authority)
+ assertEquals(null, testUri.host)
+ assertEquals(-1, testUri.port)
+ assertEquals(null, testUri.userInfo)
+ assertEquals("notifications", testUri.path)
+ assertEquals(null, testUri.fragment)
+ assertEquals("account=npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z", testUri.rawQuery)
+
+ assertEquals("npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z", testUri.findParameterValue("account"))
+ }
+}
diff --git a/amethyst/src/androidTestPlay/java/com/vitorpamplona/amethyst/TranslationsTest.kt b/amethyst/src/androidTestPlay/java/com/vitorpamplona/amethyst/TranslationsTest.kt
index 3d22f9ae0..0c48f3f55 100644
--- a/amethyst/src/androidTestPlay/java/com/vitorpamplona/amethyst/TranslationsTest.kt
+++ b/amethyst/src/androidTestPlay/java/com/vitorpamplona/amethyst/TranslationsTest.kt
@@ -27,6 +27,7 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
+import java.util.concurrent.CancellationException
@RunWith(AndroidJUnit4::class)
class TranslationsTest {
@@ -124,7 +125,10 @@ class TranslationsTest {
"Have you seen this: lnbc12u1p3lvjeupp5a5ecgp45k6pa8tu7rnkgzfuwdy3l5ylv3k5tdzrg4cr8rj2f364sdq5g9kxy7fqd9h8vmmfvdjscqzpgxqyz5vqsp5zuzyetf33aphetf0e80w7tztw6dfsjs4lmvya4cyk8umfsx00qts9qyyssqke9hphcr36zvcav8wr502g0mhfhxpy8m9tt36zttg8vldm2qxw039ulccr8nwy3hjg2sw5vk65e99lwuhrhw0nuya2u57qszltvx7egp74jydn I think I have to pay",
"pt",
)
+ }
+ @Test(expected = CancellationException::class)
+ fun testTranslationLnInvoice2() {
assertTranslateContains(
"lnbc10u1p3l0wg0pp5y5y3vxt3429m28uuq56uqhwxadftn67yaarq06h3y9nqapz72n6sdqqxqyjw5q9q7sqqqqqqqqqqqqqqqqqqqqqqqqq9qsqsp5y2tazp42xde3c0tdsz30zqcekrt0lzrneszdtagy2qn7vs0d3p5qrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glcll7jdvcln4lhw5qqqqlgqqqqqeqqjqdau9jzseecmvmh03h88xyf5f980xx45fmn0cej654v5jr79ye36pww90jwdda38damlmgt54v8rn6q9kywtw057rh4v3wwrmn8fajagqnssr7v",
"Test lnbc10u1p3l0wg0pp5y5y3vxt3429m28uuq56uqhwxadftn67yaarq06h3y9nqapz72n6sdqqxqyjw5q9q7sqqqqqqqqqqqqqqqqqqqqqqqqq9qsqsp5y2tazp42xde3c0tdsz30zqcekrt0lzrneszdtagy2qn7vs0d3p5qrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glcll7jdvcln4lhw5qqqqlgqqqqqeqqjqdau9jzseecmvmh03h88xyf5f980xx45fmn0cej654v5jr79ye36pww90jwdda38damlmgt54v8rn6q9kywtw057rh4v3wwrmn8fajagqnssr7v",
@@ -132,7 +136,7 @@ class TranslationsTest {
)
}
- @Test
+ @Test(expected = CancellationException::class)
fun testNostrEvents() {
assertTranslateContains(
"nostr:nevent1qqs0tsw8hjacs4fppgdg7f5yhgwwfkyua4xcs3re9wwkpkk2qeu6mhql22rcy",
@@ -162,4 +166,15 @@ class TranslationsTest {
"pt",
)
}
+
+ @Test
+ fun testHttp() {
+ val text = "https://m.primal.net/MdDd.png \nRunning... \uD83D\uDE01 nostr:npub126ntw5mnermmj0znhjhgdk8lh2af72sm8qfzq48umdlnhaj9kuns3le9ll nostr:npub1getal6ykt05fsz5nqu4uld09nfj3y3qxmv8crys4aeut53unfvlqr80nfm"
+
+ assertTranslateContains(
+ "https://m.primal.net/MdDd.png",
+ text,
+ "pt",
+ )
+ }
}
diff --git a/amethyst/src/fdroid/AndroidManifest.xml b/amethyst/src/fdroid/AndroidManifest.xml
index c4ef213f9..62202f91f 100644
--- a/amethyst/src/fdroid/AndroidManifest.xml
+++ b/amethyst/src/fdroid/AndroidManifest.xml
@@ -1,6 +1,5 @@
-
+
diff --git a/amethyst/src/main/AndroidManifest.xml b/amethyst/src/main/AndroidManifest.xml
index 049a2f5ad..d5249d412 100644
--- a/amethyst/src/main/AndroidManifest.xml
+++ b/amethyst/src/main/AndroidManifest.xml
@@ -19,12 +19,8 @@
-
-
-
-
-
+
@@ -120,10 +116,6 @@
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt
index 1de3edec1..571246428 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt
@@ -23,6 +23,8 @@ package com.vitorpamplona.amethyst
import android.app.Application
import android.content.ContentResolver
import android.content.Context
+import android.content.IntentFilter
+import android.os.Build
import android.os.Looper
import android.os.StrictMode
import android.os.StrictMode.ThreadPolicy
@@ -31,12 +33,13 @@ import android.util.Log
import androidx.security.crypto.EncryptedSharedPreferences
import coil3.ImageLoader
import coil3.disk.DiskCache
-import coil3.disk.directory
import coil3.memory.MemoryCache
-import coil3.request.crossfade
import com.vitorpamplona.amethyst.service.LocationState
+import com.vitorpamplona.amethyst.service.notifications.PokeyReceiver
+import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
+import com.vitorpamplona.amethyst.service.okhttp.OkHttpWebSocket
import com.vitorpamplona.amethyst.service.playback.VideoCache
-import com.vitorpamplona.ammolite.service.HttpClientManager
+import com.vitorpamplona.ammolite.relays.NostrClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
@@ -52,12 +55,17 @@ import kotlin.time.measureTimedValue
class Amethyst : Application() {
val applicationIOScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
+ val client: NostrClient = NostrClient(OkHttpWebSocket.Builder())
+
// Service Manager is only active when the activity is active.
- val serviceManager = ServiceManager(applicationIOScope)
+ val serviceManager = ServiceManager(client, applicationIOScope)
val locationManager = LocationState(this, applicationIOScope)
+ val pokeyReceiver = PokeyReceiver()
+
override fun onTerminate() {
super.onTerminate()
+ unregisterReceiver(pokeyReceiver)
applicationIOScope.cancel()
}
@@ -126,6 +134,19 @@ class Amethyst : Application() {
}
Log.d("Rendering Metrics", "VideoCache initialized in $elapsed")
}
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ registerReceiver(
+ pokeyReceiver,
+ IntentFilter(PokeyReceiver.POKEY_ACTION),
+ RECEIVER_EXPORTED,
+ )
+ } else {
+ registerReceiver(
+ pokeyReceiver,
+ IntentFilter(PokeyReceiver.POKEY_ACTION),
+ )
+ }
}
fun imageLoaderBuilder(): ImageLoader.Builder =
@@ -133,7 +154,6 @@ class Amethyst : Application() {
.Builder(this)
.diskCache { coilCache }
.memoryCache { memoryCache }
- .crossfade(true)
fun encryptedStorage(npub: String? = null): EncryptedSharedPreferences = EncryptedStorage.preferences(instance, npub)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/DebugUtils.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/DebugUtils.kt
index e6998b047..0c63f8b20 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/DebugUtils.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/DebugUtils.kt
@@ -43,11 +43,9 @@ import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
import com.vitorpamplona.amethyst.service.NostrThreadDataSource
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
import com.vitorpamplona.amethyst.service.NostrVideoDataSource
-import com.vitorpamplona.ammolite.relays.Client
-import com.vitorpamplona.ammolite.relays.RelayPool
fun debugState(context: Context) {
- Client
+ Amethyst.instance.client
.allSubscriptions()
.forEach { Log.d("STATE DUMP", "${it.key} ${it.value.joinToString { it.filter.toDebugJson() }}") }
@@ -89,7 +87,7 @@ fun debugState(context: Context) {
Log.d("STATE DUMP", "Memory Class " + memClass + " MB (largeHeap $isLargeHeap)")
}
- Log.d("STATE DUMP", "Connected Relays: " + RelayPool.connectedRelays())
+ Log.d("STATE DUMP", "Connected Relays: " + Amethyst.instance.client.connectedRelays())
Log.d(
"STATE DUMP",
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt
index abef26950..7413c1a4b 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt
@@ -37,8 +37,9 @@ import com.vitorpamplona.amethyst.model.DefaultZapAmounts
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS
import com.vitorpamplona.amethyst.model.Settings
-import com.vitorpamplona.amethyst.service.Nip96MediaServers
import com.vitorpamplona.amethyst.service.checkNotInMainThread
+import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
+import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
import com.vitorpamplona.amethyst.ui.tor.TorSettings
import com.vitorpamplona.amethyst.ui.tor.TorSettingsFlow
import com.vitorpamplona.amethyst.ui.tor.TorType
@@ -379,6 +380,12 @@ object LocalPreferences {
remove(PrefKeys.LATEST_SEARCH_RELAY_LIST)
}
+ if (settings.localRelayServers.isNotEmpty()) {
+ putStringSet(PrefKeys.LOCAL_RELAY_SERVERS, settings.localRelayServers)
+ } else {
+ remove(PrefKeys.LOCAL_RELAY_SERVERS)
+ }
+
if (settings.backupMuteList != null) {
putString(
PrefKeys.LATEST_MUTE_LIST,
@@ -434,6 +441,7 @@ object LocalPreferences {
}.apply()
}
}
+ Log.d("LocalPreferences", "Saved to encrypted storage")
}
suspend fun loadCurrentAccountFromEncryptedStorage(): AccountSettings? = currentAccount()?.let { loadCurrentAccountFromEncryptedStorage(it) }
@@ -520,7 +528,7 @@ object LocalPreferences {
val localRelays = parseOrNull>(PrefKeys.RELAYS) ?: emptySet()
val zapPaymentRequestServer = parseOrNull(PrefKeys.ZAP_PAYMENT_REQUEST_SERVER)
- val defaultFileServer = parseOrNull(PrefKeys.DEFAULT_FILE_SERVER) ?: Nip96MediaServers.DEFAULT[0]
+ val defaultFileServer = parseOrNull(PrefKeys.DEFAULT_FILE_SERVER) ?: DEFAULT_MEDIA_SERVERS[0]
val pendingAttestations = parseOrNull