Merge branch 'main' into adding_translations
This commit is contained in:
+65
-64
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
alias(libs.plugins.androidApplication)
|
||||||
id 'org.jetbrains.kotlin.android'
|
alias(libs.plugins.jetbrainsKotlinAndroid)
|
||||||
id 'com.google.gms.google-services'
|
alias(libs.plugins.googleServices)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -145,14 +145,13 @@ android {
|
|||||||
// Should match compose version : https://developer.android.com/jetpack/androidx/releases/compose-kotlin
|
// Should match compose version : https://developer.android.com/jetpack/androidx/releases/compose-kotlin
|
||||||
kotlinCompilerExtensionVersion "1.5.8"
|
kotlinCompilerExtensionVersion "1.5.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
resources {
|
resources {
|
||||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
excludes += ['/META-INF/{AL2.0,LGPL2.1}', '**/libscrypt.dylib']
|
||||||
}
|
}
|
||||||
exclude '**/libscrypt.dylib'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
lint {
|
lint {
|
||||||
disable 'MissingTranslation'
|
disable 'MissingTranslation'
|
||||||
}
|
}
|
||||||
@@ -165,75 +164,78 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':quartz')
|
implementation project(path: ':quartz')
|
||||||
implementation project(path: ':commons')
|
implementation project(path: ':commons')
|
||||||
implementation "androidx.core:core-ktx:$core_ktx_version"
|
implementation libs.androidx.core.ktx
|
||||||
implementation 'androidx.activity:activity-compose:1.8.2'
|
implementation libs.androidx.activity.compose
|
||||||
implementation "androidx.compose.ui:ui:$compose_ui_version"
|
|
||||||
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
|
implementation platform(libs.androidx.compose.bom)
|
||||||
|
|
||||||
|
implementation libs.androidx.ui
|
||||||
|
implementation libs.androidx.ui.graphics
|
||||||
|
implementation libs.androidx.ui.tooling.preview
|
||||||
|
|
||||||
// Needs this to open gallery / image upload
|
// Needs this to open gallery / image upload
|
||||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
implementation libs.androidx.fragment.ktx
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
implementation "androidx.navigation:navigation-compose:$nav_version"
|
implementation libs.androidx.navigation.compose
|
||||||
|
|
||||||
// Observe Live data as State
|
// Observe Live data as State
|
||||||
implementation "androidx.compose.runtime:runtime-livedata:$compose_ui_version"
|
implementation libs.androidx.runtime.livedata
|
||||||
|
|
||||||
// Material 3 Design
|
// Material 3 Design
|
||||||
implementation "androidx.compose.material3:material3:${material3_version}"
|
implementation libs.androidx.material3
|
||||||
implementation "androidx.compose.material:material-icons-extended:$compose_ui_version"
|
implementation libs.androidx.material.icons
|
||||||
|
|
||||||
// Adaptive Layout / Two Pane
|
// Adaptive Layout / Two Pane
|
||||||
implementation "androidx.compose.material3:material3-window-size-class:${material3_version}"
|
implementation libs.androidx.material3.windowSize
|
||||||
implementation 'com.google.accompanist:accompanist-adaptive:0.34.0'
|
implementation libs.accompanist.adaptive
|
||||||
|
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
implementation "androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version"
|
implementation libs.androidx.lifecycle.runtime.ktx
|
||||||
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
|
implementation libs.androidx.lifecycle.runtime.compose
|
||||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
implementation libs.androidx.lifecycle.viewmodel.compose
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
implementation libs.androidx.lifecycle.livedata.ktx
|
||||||
|
|
||||||
// Zoomable images
|
// Zoomable images
|
||||||
implementation 'net.engawapg.lib:zoomable:1.6.0'
|
implementation libs.zoomable
|
||||||
|
|
||||||
// Biometrics
|
// Biometrics
|
||||||
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
implementation libs.androidx.biometric.ktx
|
||||||
|
|
||||||
// Websockets API
|
// Websockets API
|
||||||
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.12'
|
implementation libs.okhttp
|
||||||
|
|
||||||
// HTML Parsing for Link Preview
|
// HTML Parsing for Link Preview
|
||||||
implementation 'org.jsoup:jsoup:1.17.2'
|
implementation libs.jsoup
|
||||||
|
|
||||||
// Encrypted Key Storage
|
// Encrypted Key Storage
|
||||||
implementation 'androidx.security:security-crypto-ktx:1.1.0-alpha06'
|
implementation libs.androidx.security.crypto.ktx
|
||||||
|
|
||||||
// view videos
|
// view videos
|
||||||
implementation "androidx.media3:media3-exoplayer:$media3_version"
|
implementation libs.androidx.media3.exoplayer
|
||||||
implementation "androidx.media3:media3-exoplayer-hls:$media3_version"
|
implementation libs.androidx.media3.exoplayer.hls
|
||||||
implementation "androidx.media3:media3-ui:$media3_version"
|
implementation libs.androidx.media3.ui
|
||||||
implementation "androidx.media3:media3-session:$media3_version"
|
implementation libs.androidx.media3.session
|
||||||
|
|
||||||
// important for proxy / tor
|
// important for proxy / tor
|
||||||
implementation "androidx.media3:media3-datasource-okhttp:$media3_version"
|
implementation libs.androidx.media3.datasource.okhttp
|
||||||
|
|
||||||
// Load images from the web.
|
// Load images from the web.
|
||||||
implementation "io.coil-kt:coil-compose:$coil_version"
|
implementation libs.coil.compose
|
||||||
// view gifs
|
// view gifs
|
||||||
implementation "io.coil-kt:coil-gif:$coil_version"
|
implementation libs.coil.gif
|
||||||
// view svgs
|
// view svgs
|
||||||
implementation "io.coil-kt:coil-svg:$coil_version"
|
implementation libs.coil.svg
|
||||||
|
|
||||||
// create blurhash
|
// create blurhash
|
||||||
implementation group: 'io.trbl', name: 'blurhash', version: '1.0.0'
|
implementation libs.trbl.blurhash
|
||||||
|
|
||||||
// Permission to upload pictures:
|
// Permission to upload pictures:
|
||||||
implementation "com.google.accompanist:accompanist-permissions:$accompanist_version"
|
implementation libs.accompanist.permissions
|
||||||
|
|
||||||
// For QR generation
|
// For QR generation
|
||||||
implementation 'com.google.zxing:core:3.5.3'
|
implementation libs.zxing
|
||||||
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
|
implementation libs.zxing.embedded
|
||||||
|
|
||||||
// Markdown
|
// Markdown
|
||||||
//implementation "com.halilibo.compose-richtext:richtext-ui:0.16.0"
|
//implementation "com.halilibo.compose-richtext:richtext-ui:0.16.0"
|
||||||
@@ -241,51 +243,50 @@ dependencies {
|
|||||||
//implementation "com.halilibo.compose-richtext:richtext-commonmark:0.16.0"
|
//implementation "com.halilibo.compose-richtext:richtext-commonmark:0.16.0"
|
||||||
|
|
||||||
// Markdown (With fix for full-image bleeds)
|
// Markdown (With fix for full-image bleeds)
|
||||||
implementation('com.github.vitorpamplona.compose-richtext:richtext-ui:48702a8ced')
|
implementation libs.markdown.ui
|
||||||
implementation('com.github.vitorpamplona.compose-richtext:richtext-ui-material3:48702a8ced')
|
implementation libs.markdown.ui.material3
|
||||||
implementation('com.github.vitorpamplona.compose-richtext:richtext-commonmark:48702a8ced')
|
implementation libs.markdown.commonmark
|
||||||
|
|
||||||
// Language picker and Theme chooser
|
// Language picker and Theme chooser
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation libs.androidx.appcompat
|
||||||
|
|
||||||
// Local model for language identification
|
// Local model for language identification
|
||||||
playImplementation 'com.google.mlkit:language-id:17.0.5'
|
playImplementation libs.google.mlkit.language.id
|
||||||
|
|
||||||
// Google services model the translate text
|
// Google services model the translate text
|
||||||
playImplementation 'com.google.mlkit:translate:17.0.2'
|
playImplementation libs.google.mlkit.translate
|
||||||
|
|
||||||
// PushNotifications
|
// PushNotifications
|
||||||
playImplementation platform('com.google.firebase:firebase-bom:32.7.2')
|
playImplementation platform(libs.firebase.bom)
|
||||||
playImplementation 'com.google.firebase:firebase-messaging-ktx'
|
playImplementation libs.firebase.messaging
|
||||||
|
|
||||||
//PushNotifications(FDroid)
|
//PushNotifications(FDroid)
|
||||||
fdroidImplementation 'com.github.UnifiedPush:android-connector:2.2.0'
|
fdroidImplementation libs.unifiedpush
|
||||||
|
|
||||||
// Charts
|
// Charts
|
||||||
implementation "com.patrykandpatrick.vico:core:${vico_version}"
|
implementation libs.vico.charts.core
|
||||||
implementation "com.patrykandpatrick.vico:compose:${vico_version}"
|
implementation libs.vico.charts.compose
|
||||||
implementation "com.patrykandpatrick.vico:views:${vico_version}"
|
implementation libs.vico.charts.views
|
||||||
implementation "com.patrykandpatrick.vico:compose-m2:${vico_version}"
|
implementation libs.vico.charts.m3
|
||||||
|
|
||||||
// GeoHash
|
// GeoHash
|
||||||
implementation 'com.github.drfonfon:android-kotlin-geohash:1.0'
|
implementation libs.drfonfon.geohash
|
||||||
|
|
||||||
// Waveform visualizer
|
// Waveform visualizer
|
||||||
implementation 'com.github.lincollincol:compose-audiowaveform:1.1.1'
|
implementation libs.audiowaveform
|
||||||
|
|
||||||
// Video compression lib
|
// Video compression lib
|
||||||
implementation 'com.github.AbedElazizShe:LightCompressor:1.3.2'
|
implementation libs.abedElazizShe.image.compressor
|
||||||
// Image compression lib
|
// Image compression lib
|
||||||
implementation 'id.zelory:compressor:3.0.1'
|
implementation libs.zelory.video.compressor
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation libs.junit
|
||||||
testImplementation 'io.mockk:mockk:1.13.9'
|
testImplementation libs.mockk
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.2.0-alpha03'
|
androidTestImplementation libs.androidx.junit
|
||||||
androidTestImplementation 'androidx.test.ext:junit-ktx:1.2.0-alpha03'
|
androidTestImplementation libs.androidx.junit.ktx
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
androidTestImplementation libs.androidx.espresso.core
|
||||||
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version"
|
debugImplementation libs.androidx.ui.tooling
|
||||||
debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version"
|
debugImplementation libs.androidx.ui.test.manifest
|
||||||
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://gitlab.com/fdroid/wiki/-/wikis/HOWTO:-diff-&-fix-APKs-for-Reproducible-Builds#differing-assetsdexoptbaselineprofm-easy-to-fix
|
// https://gitlab.com/fdroid/wiki/-/wikis/HOWTO:-diff-&-fix-APKs-for-Reproducible-Builds#differing-assetsdexoptbaselineprofm-easy-to-fix
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
package com.vitorpamplona.amethyst.model
|
package com.vitorpamplona.amethyst.model
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.util.LruCache
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
@@ -1402,6 +1403,13 @@ object LocalCache {
|
|||||||
|
|
||||||
note.loadEvent(event, author, emptyList())
|
note.loadEvent(event, author, emptyList())
|
||||||
|
|
||||||
|
event.editedNote()?.let {
|
||||||
|
getNoteIfExists(it)?.let { editedNote ->
|
||||||
|
modificationCache.remove(editedNote.idHex)
|
||||||
|
editedNote.liveSet?.innerModifications?.invalidateData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
refreshObservers(note)
|
refreshObservers(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1716,15 +1724,31 @@ object LocalCache {
|
|||||||
return minTime
|
return minTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val modificationCache = LruCache<HexKey, List<Note>>(20)
|
||||||
|
|
||||||
|
fun cachedModificationEventsForNote(note: Note): List<Note>? {
|
||||||
|
return modificationCache[note.idHex]
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun findLatestModificationForNote(note: Note): List<Note> {
|
suspend fun findLatestModificationForNote(note: Note): List<Note> {
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
|
modificationCache[note.idHex]?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
val time = TimeUtils.now()
|
val time = TimeUtils.now()
|
||||||
|
|
||||||
return noteListCache.filter { item ->
|
val newNotes =
|
||||||
val noteEvent = item.event
|
noteListCache.filter { item ->
|
||||||
|
val noteEvent = item.event
|
||||||
|
|
||||||
noteEvent is TextNoteModificationEvent && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time)
|
noteEvent is TextNoteModificationEvent && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time)
|
||||||
}
|
}.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
|
||||||
|
|
||||||
|
modificationCache.put(note.idHex, newNotes)
|
||||||
|
|
||||||
|
return newNotes
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cleanObservers() {
|
fun cleanObservers() {
|
||||||
|
|||||||
@@ -37,26 +37,26 @@ class JsonFilter(
|
|||||||
val filter =
|
val filter =
|
||||||
factory.objectNode().apply {
|
factory.objectNode().apply {
|
||||||
ids?.run {
|
ids?.run {
|
||||||
put(
|
replace(
|
||||||
"ids",
|
"ids",
|
||||||
factory.arrayNode(ids.size).apply { ids.forEach { add(it) } },
|
factory.arrayNode(ids.size).apply { ids.forEach { add(it) } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
authors?.run {
|
authors?.run {
|
||||||
put(
|
replace(
|
||||||
"authors",
|
"authors",
|
||||||
factory.arrayNode(authors.size).apply { authors.forEach { add(it) } },
|
factory.arrayNode(authors.size).apply { authors.forEach { add(it) } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
kinds?.run {
|
kinds?.run {
|
||||||
put(
|
replace(
|
||||||
"kinds",
|
"kinds",
|
||||||
factory.arrayNode(kinds.size).apply { kinds.forEach { add(it) } },
|
factory.arrayNode(kinds.size).apply { kinds.forEach { add(it) } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
tags?.run {
|
tags?.run {
|
||||||
entries.forEach { kv ->
|
entries.forEach { kv ->
|
||||||
put(
|
replace(
|
||||||
"#${kv.key}",
|
"#${kv.key}",
|
||||||
factory.arrayNode(kv.value.size).apply { kv.value.forEach { add(it) } },
|
factory.arrayNode(kv.value.size).apply { kv.value.forEach { add(it) } },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ data class Subscription(
|
|||||||
return factory.objectNode().apply {
|
return factory.objectNode().apply {
|
||||||
put("id", id)
|
put("id", id)
|
||||||
typedFilters?.also { filters ->
|
typedFilters?.also { filters ->
|
||||||
put(
|
replace(
|
||||||
"typedFilters",
|
"typedFilters",
|
||||||
factory.arrayNode(filters.size).apply {
|
factory.arrayNode(filters.size).apply {
|
||||||
filters.forEach { filter -> add(filter.toJsonObject()) }
|
filters.forEach { filter -> add(filter.toJsonObject()) }
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ class TypedFilter(
|
|||||||
val factory = Event.mapper.nodeFactory
|
val factory = Event.mapper.nodeFactory
|
||||||
|
|
||||||
return factory.objectNode().apply {
|
return factory.objectNode().apply {
|
||||||
put("types", typesToJson(types))
|
replace("types", typesToJson(types))
|
||||||
put("filter", filterToJson(filter))
|
replace("filter", filterToJson(filter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,26 +50,26 @@ class TypedFilter(
|
|||||||
val factory = Event.mapper.nodeFactory
|
val factory = Event.mapper.nodeFactory
|
||||||
return factory.objectNode().apply {
|
return factory.objectNode().apply {
|
||||||
filter.ids?.run {
|
filter.ids?.run {
|
||||||
put(
|
replace(
|
||||||
"ids",
|
"ids",
|
||||||
factory.arrayNode(filter.ids.size).apply { filter.ids.forEach { add(it) } },
|
factory.arrayNode(filter.ids.size).apply { filter.ids.forEach { add(it) } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
filter.authors?.run {
|
filter.authors?.run {
|
||||||
put(
|
replace(
|
||||||
"authors",
|
"authors",
|
||||||
factory.arrayNode(filter.authors.size).apply { filter.authors.forEach { add(it) } },
|
factory.arrayNode(filter.authors.size).apply { filter.authors.forEach { add(it) } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
filter.kinds?.run {
|
filter.kinds?.run {
|
||||||
put(
|
replace(
|
||||||
"kinds",
|
"kinds",
|
||||||
factory.arrayNode(filter.kinds.size).apply { filter.kinds.forEach { add(it) } },
|
factory.arrayNode(filter.kinds.size).apply { filter.kinds.forEach { add(it) } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
filter.tags?.run {
|
filter.tags?.run {
|
||||||
entries.forEach { kv ->
|
entries.forEach { kv ->
|
||||||
put(
|
replace(
|
||||||
"#${kv.key}",
|
"#${kv.key}",
|
||||||
factory.arrayNode(kv.value.size).apply { kv.value.forEach { add(it) } },
|
factory.arrayNode(kv.value.size).apply { kv.value.forEach { add(it) } },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
@@ -1088,10 +1089,58 @@ fun InnerNoteWithReactions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class EditState(
|
class EditState() {
|
||||||
val showOriginal: MutableState<Boolean> = mutableStateOf(false),
|
private var modificationsList: List<Note> = persistentListOf()
|
||||||
val modificationsInOrder: MutableState<List<Note>> = mutableStateOf(emptyList()),
|
private var modificationToShowIndex: Int = -1
|
||||||
)
|
|
||||||
|
val modificationToShow: MutableState<Note?> = mutableStateOf(null)
|
||||||
|
val showingVersion: MutableState<Int> = mutableStateOf(0)
|
||||||
|
|
||||||
|
fun hasModificationsToShow(): Boolean = modificationsList.isNotEmpty()
|
||||||
|
|
||||||
|
fun isOriginal(): Boolean = modificationToShowIndex < 0
|
||||||
|
|
||||||
|
fun isLatest(): Boolean = modificationToShowIndex == modificationsList.lastIndex
|
||||||
|
|
||||||
|
fun originalVersionId() = 0
|
||||||
|
|
||||||
|
fun lastVersionId() = modificationsList.size
|
||||||
|
|
||||||
|
fun versionId() = modificationToShowIndex + 1
|
||||||
|
|
||||||
|
fun nextModification() {
|
||||||
|
if (modificationToShowIndex < 0) {
|
||||||
|
modificationToShowIndex = 0
|
||||||
|
modificationToShow.value = modificationsList.getOrNull(0)
|
||||||
|
} else {
|
||||||
|
modificationToShowIndex++
|
||||||
|
if (modificationToShowIndex >= modificationsList.size) {
|
||||||
|
modificationToShowIndex = -1
|
||||||
|
modificationToShow.value = null
|
||||||
|
} else {
|
||||||
|
modificationToShow.value = modificationsList.getOrNull(modificationToShowIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showingVersion.value = versionId()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateModifications(newModifications: List<Note>) {
|
||||||
|
if (modificationsList != newModifications) {
|
||||||
|
modificationsList = newModifications
|
||||||
|
|
||||||
|
if (newModifications.isEmpty()) {
|
||||||
|
modificationToShow.value = null
|
||||||
|
modificationToShowIndex = -1
|
||||||
|
} else {
|
||||||
|
modificationToShowIndex = newModifications.lastIndex
|
||||||
|
modificationToShow.value = newModifications.last()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showingVersion.value = versionId()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NoteBody(
|
private fun NoteBody(
|
||||||
@@ -1105,14 +1154,7 @@ private fun NoteBody(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val editState by
|
val editState = observeEdits(baseNote = baseNote, accountViewModel = accountViewModel)
|
||||||
produceState(initialValue = EditState(), key1 = baseNote) {
|
|
||||||
accountViewModel.findModificationEventsForNote(baseNote) { newModifications ->
|
|
||||||
if (value.modificationsInOrder.value != newModifications) {
|
|
||||||
value.modificationsInOrder.value = newModifications
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FirstUserInfoRow(
|
FirstUserInfoRow(
|
||||||
baseNote = baseNote,
|
baseNote = baseNote,
|
||||||
@@ -1168,7 +1210,7 @@ private fun RenderNoteRow(
|
|||||||
backgroundColor: MutableState<Color>,
|
backgroundColor: MutableState<Color>,
|
||||||
makeItShort: Boolean,
|
makeItShort: Boolean,
|
||||||
canPreview: Boolean,
|
canPreview: Boolean,
|
||||||
editState: EditState,
|
editState: State<GenericLoadable<EditState>>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -1358,7 +1400,7 @@ fun RenderTextEvent(
|
|||||||
makeItShort: Boolean,
|
makeItShort: Boolean,
|
||||||
canPreview: Boolean,
|
canPreview: Boolean,
|
||||||
backgroundColor: MutableState<Color>,
|
backgroundColor: MutableState<Color>,
|
||||||
editState: EditState,
|
editState: State<GenericLoadable<EditState>>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -1371,10 +1413,10 @@ fun RenderTextEvent(
|
|||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
val subject = (note.event as? TextNoteEvent)?.subject()?.ifEmpty { null }
|
val subject = (note.event as? TextNoteEvent)?.subject()?.ifEmpty { null }
|
||||||
val newBody =
|
val newBody =
|
||||||
if (editState.showOriginal.value || editState.modificationsInOrder.value.isEmpty()) {
|
if (editState.value is GenericLoadable.Loaded) {
|
||||||
body
|
(editState.value as? GenericLoadable.Loaded)?.loaded?.modificationToShow?.value?.event?.content() ?: body
|
||||||
} else {
|
} else {
|
||||||
editState.modificationsInOrder.value.firstOrNull()?.event?.content() ?: body
|
body
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!subject.isNullOrBlank() && !newBody.split("\n")[0].contains(subject)) {
|
if (!subject.isNullOrBlank() && !newBody.split("\n")[0].contains(subject)) {
|
||||||
@@ -2822,7 +2864,7 @@ fun DisplayLocation(
|
|||||||
fun FirstUserInfoRow(
|
fun FirstUserInfoRow(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
showAuthorPicture: Boolean,
|
showAuthorPicture: Boolean,
|
||||||
editState: EditState,
|
editState: State<GenericLoadable<EditState>>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -2857,8 +2899,10 @@ fun FirstUserInfoRow(
|
|||||||
DisplayFollowingHashtagsInPost(baseNote, accountViewModel, nav)
|
DisplayFollowingHashtagsInPost(baseNote, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!editState.modificationsInOrder.value.isEmpty()) {
|
if (editState.value is GenericLoadable.Loaded) {
|
||||||
DisplayEditStatus(editState.showOriginal)
|
(editState.value as? GenericLoadable.Loaded<EditState>)?.loaded?.let {
|
||||||
|
DisplayEditStatus(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeAgo(baseNote)
|
TimeAgo(baseNote)
|
||||||
@@ -2868,15 +2912,69 @@ fun FirstUserInfoRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DisplayEditStatus(showOriginal: MutableState<Boolean>) {
|
fun observeEdits(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
): State<GenericLoadable<EditState>> {
|
||||||
|
val editState =
|
||||||
|
remember(baseNote.idHex) {
|
||||||
|
val cached = accountViewModel.cachedModificationEventsForNote(baseNote)
|
||||||
|
mutableStateOf(
|
||||||
|
if (cached != null) {
|
||||||
|
if (cached.isEmpty()) {
|
||||||
|
GenericLoadable.Empty<EditState>()
|
||||||
|
} else {
|
||||||
|
val state = EditState()
|
||||||
|
state.updateModifications(cached)
|
||||||
|
GenericLoadable.Loaded<EditState>(state)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GenericLoadable.Loading<EditState>()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val updatedNote = baseNote.live().innerModifications.observeAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = updatedNote) {
|
||||||
|
updatedNote.value?.note?.let {
|
||||||
|
accountViewModel.findModificationEventsForNote(it) { newModifications ->
|
||||||
|
if (newModifications.isEmpty()) {
|
||||||
|
if (editState.value !is GenericLoadable.Empty) {
|
||||||
|
editState.value = GenericLoadable.Empty<EditState>()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (editState.value is GenericLoadable.Loaded) {
|
||||||
|
(editState.value as? GenericLoadable.Loaded<EditState>)?.loaded?.updateModifications(newModifications)
|
||||||
|
} else {
|
||||||
|
val state = EditState()
|
||||||
|
state.updateModifications(newModifications)
|
||||||
|
editState.value = GenericLoadable.Loaded(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return editState
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DisplayEditStatus(editState: EditState) {
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text =
|
text =
|
||||||
if (showOriginal.value) {
|
buildAnnotatedString {
|
||||||
buildAnnotatedString { append(stringResource(id = R.string.original)) }
|
if (editState.showingVersion.value == editState.originalVersionId()) {
|
||||||
} else {
|
append(stringResource(id = R.string.original))
|
||||||
buildAnnotatedString { append(stringResource(id = R.string.edited)) }
|
} else if (editState.showingVersion.value == editState.lastVersionId()) {
|
||||||
|
append(stringResource(id = R.string.edited))
|
||||||
|
} else {
|
||||||
|
append(stringResource(id = R.string.edited_number, editState.versionId()))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onClick = { showOriginal.value = !showOriginal.value },
|
onClick = {
|
||||||
|
editState.nextModification()
|
||||||
|
},
|
||||||
style =
|
style =
|
||||||
LocalTextStyle.current.copy(
|
LocalTextStyle.current.copy(
|
||||||
color = MaterialTheme.colorScheme.placeholderText,
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
|||||||
@@ -111,14 +111,17 @@ class PollNoteViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tallies.forEach {
|
tallies.forEach {
|
||||||
it.zappedValue.value = zappedPollOptionAmount(it.option)
|
val zappedValue = zappedPollOptionAmount(it.option)
|
||||||
it.tally.value =
|
val tallyValue =
|
||||||
if (totalZapped.compareTo(BigDecimal.ZERO) > 0) {
|
if (totalZapped > BigDecimal.ZERO) {
|
||||||
it.zappedValue.value.divide(totalZapped, 2, RoundingMode.HALF_UP)
|
zappedValue.divide(totalZapped, 2, RoundingMode.HALF_UP)
|
||||||
} else {
|
} else {
|
||||||
BigDecimal.ZERO
|
BigDecimal.ZERO
|
||||||
}
|
}
|
||||||
it.consensusThreadhold.value = consensusThreshold != null && it.tally.value >= consensusThreshold!!
|
|
||||||
|
it.zappedValue.value = zappedValue
|
||||||
|
it.tally.value = tallyValue
|
||||||
|
it.consensusThreadhold.value = consensusThreshold != null && tallyValue >= consensusThreshold!!
|
||||||
it.zappedByLoggedIn.value = account?.userProfile()?.let { it1 -> cachedIsPollOptionZappedBy(it.option, it1) } ?: false
|
it.zappedByLoggedIn.value = account?.userProfile()?.let { it1 -> cachedIsPollOptionZappedBy(it.option, it1) } ?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -483,7 +483,10 @@ fun NoteDropDownMenu(
|
|||||||
|
|
||||||
if (wantsToEditPost.value) {
|
if (wantsToEditPost.value) {
|
||||||
EditPostView(
|
EditPostView(
|
||||||
onClose = { wantsToEditPost.value = false },
|
onClose = {
|
||||||
|
popupExpanded.value = false
|
||||||
|
wantsToEditPost.value = false
|
||||||
|
},
|
||||||
edit = note,
|
edit = note,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ import androidx.compose.runtime.derivedStateOf
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -86,6 +85,7 @@ import coil.compose.AsyncImage
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||||
import com.vitorpamplona.amethyst.ui.components.InlineCarrousel
|
import com.vitorpamplona.amethyst.ui.components.InlineCarrousel
|
||||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||||
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
||||||
@@ -132,6 +132,7 @@ import com.vitorpamplona.amethyst.ui.note.RenderPostApproval
|
|||||||
import com.vitorpamplona.amethyst.ui.note.RenderRepost
|
import com.vitorpamplona.amethyst.ui.note.RenderRepost
|
||||||
import com.vitorpamplona.amethyst.ui.note.RenderTextEvent
|
import com.vitorpamplona.amethyst.ui.note.RenderTextEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.VideoDisplay
|
import com.vitorpamplona.amethyst.ui.note.VideoDisplay
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.observeEdits
|
||||||
import com.vitorpamplona.amethyst.ui.note.showAmount
|
import com.vitorpamplona.amethyst.ui.note.showAmount
|
||||||
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -377,20 +378,13 @@ fun NoteMaster(
|
|||||||
onClick = { showHiddenNote = true },
|
onClick = { showHiddenNote = true },
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val editState by
|
|
||||||
produceState(initialValue = EditState(), key1 = baseNote) {
|
|
||||||
accountViewModel.findModificationEventsForNote(baseNote) { newModifications ->
|
|
||||||
if (value.modificationsInOrder.value != newModifications) {
|
|
||||||
value.modificationsInOrder.value = newModifications
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier
|
modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = 10.dp),
|
.padding(top = 10.dp),
|
||||||
) {
|
) {
|
||||||
|
val editState = observeEdits(baseNote = baseNote, accountViewModel = accountViewModel)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
@@ -421,8 +415,10 @@ fun NoteMaster(
|
|||||||
DisplayFollowingHashtagsInPost(baseNote, accountViewModel, nav)
|
DisplayFollowingHashtagsInPost(baseNote, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!editState.modificationsInOrder.value.isEmpty()) {
|
if (editState.value is GenericLoadable.Loaded) {
|
||||||
DisplayEditStatus(editState.showOriginal)
|
(editState.value as? GenericLoadable.Loaded<EditState>)?.loaded?.let {
|
||||||
|
DisplayEditStatus(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
@@ -850,9 +846,9 @@ private fun RenderWikiHeaderForThreadPreview() {
|
|||||||
val accountViewModel = mockAccountViewModel()
|
val accountViewModel = mockAccountViewModel()
|
||||||
val nav: (String) -> Unit = {}
|
val nav: (String) -> Unit = {}
|
||||||
|
|
||||||
val editState by
|
val editState =
|
||||||
remember {
|
remember {
|
||||||
mutableStateOf(EditState())
|
mutableStateOf(GenericLoadable.Empty<EditState>())
|
||||||
}
|
}
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
|||||||
@@ -932,6 +932,8 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun cachedModificationEventsForNote(note: Note) = LocalCache.cachedModificationEventsForNote(note)
|
||||||
|
|
||||||
suspend fun findModificationEventsForNote(
|
suspend fun findModificationEventsForNote(
|
||||||
note: Note,
|
note: Note,
|
||||||
onResult: (List<Note>) -> Unit,
|
onResult: (List<Note>) -> Unit,
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
<string name="boost">Boost</string>
|
<string name="boost">Boost</string>
|
||||||
<string name="boosted">boosted</string>
|
<string name="boosted">boosted</string>
|
||||||
<string name="edited">edited</string>
|
<string name="edited">edited</string>
|
||||||
|
<string name="edited_number">edit #%1$s</string>
|
||||||
<string name="original">original</string>
|
<string name="original">original</string>
|
||||||
<string name="quote">Quote</string>
|
<string name="quote">Quote</string>
|
||||||
<string name="fork">Fork</string>
|
<string name="fork">Fork</string>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.library'
|
alias(libs.plugins.androidLibrary)
|
||||||
id 'androidx.benchmark'
|
alias(libs.plugins.jetbrainsKotlinAndroid)
|
||||||
id 'org.jetbrains.kotlin.android'
|
alias(libs.plugins.androidBenchmark)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -49,10 +49,10 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
androidTestImplementation libs.androidx.runner
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
androidTestImplementation libs.androidx.junit
|
||||||
androidTestImplementation 'junit:junit:4.13.2'
|
androidTestImplementation libs.junit
|
||||||
androidTestImplementation 'androidx.benchmark:benchmark-junit4:1.2.3'
|
androidTestImplementation libs.androidx.benchmark.junit4
|
||||||
androidTestImplementation project(path: ':quartz')
|
androidTestImplementation project(path: ':quartz')
|
||||||
androidTestImplementation project(path: ':commons')
|
androidTestImplementation project(path: ':commons')
|
||||||
|
|
||||||
|
|||||||
+7
-25
@@ -1,31 +1,13 @@
|
|||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
buildscript {
|
|
||||||
ext {
|
|
||||||
fragment_version = "1.6.2"
|
|
||||||
lifecycle_version = '2.7.0'
|
|
||||||
compose_ui_version = '1.6.2'
|
|
||||||
nav_version = '2.7.7'
|
|
||||||
room_version = "2.4.3"
|
|
||||||
accompanist_version = '0.34.0'
|
|
||||||
coil_version = '2.6.0'
|
|
||||||
vico_version = '1.14.0'
|
|
||||||
media3_version = '1.2.1'
|
|
||||||
core_ktx_version = '1.12.0'
|
|
||||||
material3_version = '1.2.0'
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.google.gms:google-services:4.4.1'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '8.2.2' apply false
|
alias(libs.plugins.androidApplication) apply false
|
||||||
id 'com.android.library' version '8.2.2' apply false
|
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
|
||||||
id 'org.jetbrains.kotlin.android' version '1.9.22' apply false
|
alias(libs.plugins.androidLibrary) apply false
|
||||||
id 'org.jetbrains.kotlin.jvm' version '1.9.22' apply false
|
alias(libs.plugins.jetbrainsKotlinJvm) apply false
|
||||||
id 'androidx.benchmark' version '1.2.3' apply false
|
alias(libs.plugins.androidBenchmark) apply false
|
||||||
id 'com.diffplug.spotless' version '6.25.0' apply false
|
alias(libs.plugins.diffplugSpotless) apply false
|
||||||
|
alias(libs.plugins.googleServices) apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.library'
|
alias(libs.plugins.androidLibrary)
|
||||||
id 'org.jetbrains.kotlin.android'
|
alias(libs.plugins.jetbrainsKotlinAndroid)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -38,12 +38,13 @@ dependencies {
|
|||||||
implementation project(path: ':quartz')
|
implementation project(path: ':quartz')
|
||||||
|
|
||||||
// Import @Immutable and @Stable
|
// Import @Immutable and @Stable
|
||||||
implementation "androidx.compose.ui:ui:$compose_ui_version"
|
implementation platform(libs.androidx.compose.bom)
|
||||||
|
implementation libs.androidx.ui
|
||||||
|
|
||||||
// immutable collections to avoid recomposition
|
// immutable collections to avoid recomposition
|
||||||
api('org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7')
|
api libs.kotlinx.collections.immutable
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation libs.junit
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
androidTestImplementation libs.androidx.junit
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
androidTestImplementation libs.androidx.espresso.core
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
[versions]
|
||||||
|
accompanistAdaptive = "0.34.0"
|
||||||
|
activityCompose = "1.8.2"
|
||||||
|
agp = "8.3.0"
|
||||||
|
androidKotlinGeohash = "1.0"
|
||||||
|
androidLifecycle = "2.7.0"
|
||||||
|
androidxJunit = "1.2.0-alpha03"
|
||||||
|
appcompat = "1.6.1"
|
||||||
|
audiowaveform = "1.1.1"
|
||||||
|
benchmark = "1.2.3"
|
||||||
|
benchmarkJunit4 = "1.2.3"
|
||||||
|
biometricKtx = "1.2.0-alpha05"
|
||||||
|
blurhash = "1.0.0"
|
||||||
|
coil = "2.6.0"
|
||||||
|
composeBom = "2024.02.01"
|
||||||
|
coreKtx = "1.12.0"
|
||||||
|
espressoCore = "3.5.1"
|
||||||
|
firebaseBom = "32.7.3"
|
||||||
|
fragmentKtx = "1.6.2"
|
||||||
|
gms = "4.4.1"
|
||||||
|
jacksonModuleKotlin = "2.16.1"
|
||||||
|
jna = "5.14.0"
|
||||||
|
jsoup = "1.17.2"
|
||||||
|
junit = "4.13.2"
|
||||||
|
kotlin = "1.9.22"
|
||||||
|
kotlinxCollectionsImmutable = "0.3.7"
|
||||||
|
languageId = "17.0.5"
|
||||||
|
lazysodiumAndroid = "5.1.0"
|
||||||
|
lightcompressor = "1.3.2"
|
||||||
|
markdown = "48702a8ced"
|
||||||
|
media3 = "1.2.1"
|
||||||
|
mockk = "1.13.9"
|
||||||
|
navigationCompose = "2.7.7"
|
||||||
|
okhttp = "5.0.0-alpha.12"
|
||||||
|
runner = "1.5.2"
|
||||||
|
secp256k1KmpJniAndroid = "0.14.0"
|
||||||
|
securityCryptoKtx = "1.1.0-alpha06"
|
||||||
|
spotless = "6.25.0"
|
||||||
|
translate = "17.0.2"
|
||||||
|
unifiedpush = "2.2.0"
|
||||||
|
urlDetector = "0.1.23"
|
||||||
|
vico-charts = "1.14.0"
|
||||||
|
zelory = "3.0.1"
|
||||||
|
zoomable = "1.6.0"
|
||||||
|
zxing = "3.5.3"
|
||||||
|
zxingAndroidEmbedded = "4.3.0"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
abedElazizShe-image-compressor = { group = "com.github.AbedElazizShe", name = "LightCompressor", version.ref = "lightcompressor" }
|
||||||
|
accompanist-adaptive = { group = "com.google.accompanist", name = "accompanist-adaptive", version.ref = "accompanistAdaptive" }
|
||||||
|
accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanistAdaptive" }
|
||||||
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||||
|
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
|
androidx-benchmark-junit4 = { group = "androidx.benchmark", name = "benchmark-junit4", version.ref = "benchmarkJunit4" }
|
||||||
|
androidx-biometric-ktx = { group = "androidx.biometric", name = "biometric-ktx", version.ref = "biometricKtx" }
|
||||||
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||||
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||||
|
androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" }
|
||||||
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" }
|
||||||
|
androidx-junit-ktx = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "androidxJunit" }
|
||||||
|
androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "androidLifecycle" }
|
||||||
|
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidLifecycle" }
|
||||||
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "androidLifecycle" }
|
||||||
|
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidLifecycle" }
|
||||||
|
androidx-material-icons = { group = "androidx.compose.material", name = "material-icons-extended" }
|
||||||
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
|
androidx-material3-windowSize = { group = "androidx.compose.material3", name = "material3-window-size-class" }
|
||||||
|
androidx-media3-datasource-okhttp = { group = "androidx.media3", name = "media3-datasource-okhttp", version.ref = "media3" }
|
||||||
|
androidx-media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" }
|
||||||
|
androidx-media3-exoplayer-hls = { group = "androidx.media3", name = "media3-exoplayer-hls", version.ref = "media3" }
|
||||||
|
androidx-media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3" }
|
||||||
|
androidx-media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3" }
|
||||||
|
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
||||||
|
androidx-runner = { group = "androidx.test", name = "runner", version.ref = "runner" }
|
||||||
|
androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata" }
|
||||||
|
androidx-runtime-runtime = { group = "androidx.compose.runtime", name = "runtime" }
|
||||||
|
androidx-security-crypto-ktx = { group = "androidx.security", name = "security-crypto-ktx", version.ref = "securityCryptoKtx" }
|
||||||
|
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||||
|
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||||
|
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||||
|
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||||
|
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||||
|
audiowaveform = { group = "com.github.lincollincol", name = "compose-audiowaveform", version.ref = "audiowaveform" }
|
||||||
|
coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
|
||||||
|
coil-gif = { group = "io.coil-kt", name = "coil-gif", version.ref = "coil" }
|
||||||
|
coil-svg = { group = "io.coil-kt", name = "coil-svg", version.ref = "coil" }
|
||||||
|
drfonfon-geohash = { group = "com.github.drfonfon", name = "android-kotlin-geohash", version.ref = "androidKotlinGeohash" }
|
||||||
|
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
|
||||||
|
firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging-ktx" }
|
||||||
|
google-mlkit-language-id = { group = "com.google.mlkit", name = "language-id", version.ref = "languageId" }
|
||||||
|
google-mlkit-translate = { group = "com.google.mlkit", name = "translate", version.ref = "translate" }
|
||||||
|
jackson-module-kotlin = { group = "com.fasterxml.jackson.module", name = "jackson-module-kotlin", version.ref = "jacksonModuleKotlin" }
|
||||||
|
jna = { group = "net.java.dev.jna", name = "jna", version.ref = "jna" }
|
||||||
|
jsoup = { group = "org.jsoup", name = "jsoup", version.ref = "jsoup" }
|
||||||
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
|
kotlinx-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" }
|
||||||
|
lazysodium-android = { group = "com.goterl", name = "lazysodium-android", version.ref = "lazysodiumAndroid" }
|
||||||
|
markdown-commonmark = { group = "com.github.vitorpamplona.compose-richtext", name = "richtext-commonmark", version.ref = "markdown" }
|
||||||
|
markdown-ui = { group = "com.github.vitorpamplona.compose-richtext", name = "richtext-ui", version.ref = "markdown" }
|
||||||
|
markdown-ui-material3 = { group = "com.github.vitorpamplona.compose-richtext", name = "richtext-ui-material3", version.ref = "markdown" }
|
||||||
|
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
|
||||||
|
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
|
||||||
|
secp256k1-kmp-jni-android = { group = "fr.acinq.secp256k1", name = "secp256k1-kmp-jni-android", version.ref = "secp256k1KmpJniAndroid" }
|
||||||
|
trbl-blurhash = { group = "io.trbl", name = "blurhash", version.ref = "blurhash" }
|
||||||
|
unifiedpush = { group = "com.github.UnifiedPush", name = "android-connector", version.ref = "unifiedpush" }
|
||||||
|
url-detector = { group = "io.github.url-detector", name = "url-detector", version.ref = "urlDetector" }
|
||||||
|
vico-charts-compose = { group = "com.patrykandpatrick.vico", name = "compose", version.ref = "vico-charts" }
|
||||||
|
vico-charts-core = { group = "com.patrykandpatrick.vico", name = "core", version.ref = "vico-charts" }
|
||||||
|
vico-charts-m3 = { group = "com.patrykandpatrick.vico", name = "compose-m3", version.ref = "vico-charts" }
|
||||||
|
vico-charts-views = { group = "com.patrykandpatrick.vico", name = "views", version.ref = "vico-charts" }
|
||||||
|
zelory-video-compressor = { group = "id.zelory", name = "compressor", version.ref = "zelory" }
|
||||||
|
zoomable = { group = "net.engawapg.lib", name = "zoomable", version.ref = "zoomable" }
|
||||||
|
zxing = { group = "com.google.zxing", name = "core", version.ref = "zxing" }
|
||||||
|
zxing-embedded = { group = "com.journeyapps", name = "zxing-android-embedded", version.ref = "zxingAndroidEmbedded" }
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
androidBenchmark = { id = "androidx.benchmark", version.ref = "benchmark" }
|
||||||
|
androidLibrary = { id = "com.android.library", version.ref = "agp" }
|
||||||
|
diffplugSpotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
|
||||||
|
googleServices = { id = "com.google.gms.google-services", version.ref = "gms" }
|
||||||
|
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
|
jetbrainsKotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
#Wed Jan 04 09:23:50 EST 2023
|
#Wed Jan 04 09:23:50 EST 2023
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
+17
-14
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.library'
|
alias(libs.plugins.androidLibrary)
|
||||||
id 'org.jetbrains.kotlin.android'
|
alias(libs.plugins.jetbrainsKotlinAndroid)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -33,36 +33,39 @@ android {
|
|||||||
jvmTarget = '17'
|
jvmTarget = '17'
|
||||||
freeCompilerArgs += "-Xstring-concat=inline"
|
freeCompilerArgs += "-Xstring-concat=inline"
|
||||||
}
|
}
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
// delete native scrypt lib because we cannot use dylibs in android
|
resources {
|
||||||
exclude '**/libscrypt.dylib'
|
excludes += ['**/libscrypt.dylib']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "androidx.core:core-ktx:$core_ktx_version"
|
implementation libs.androidx.core.ktx
|
||||||
|
|
||||||
|
implementation platform(libs.androidx.compose.bom)
|
||||||
|
|
||||||
// @Immutable and @Stable
|
// @Immutable and @Stable
|
||||||
implementation "androidx.compose.runtime:runtime:$compose_ui_version"
|
implementation libs.androidx.runtime.runtime
|
||||||
|
|
||||||
// Bitcoin secp256k1 bindings to Android
|
// Bitcoin secp256k1 bindings to Android
|
||||||
api 'fr.acinq.secp256k1:secp256k1-kmp-jni-android:0.14.0'
|
api libs.secp256k1.kmp.jni.android
|
||||||
|
|
||||||
// LibSodium for ChaCha encryption (NIP-44)
|
// LibSodium for ChaCha encryption (NIP-44)
|
||||||
|
// Wait for @aar support in version catalogs
|
||||||
implementation "com.goterl:lazysodium-android:5.1.0@aar"
|
implementation "com.goterl:lazysodium-android:5.1.0@aar"
|
||||||
implementation 'net.java.dev.jna:jna:5.14.0@aar'
|
implementation 'net.java.dev.jna:jna:5.14.0@aar'
|
||||||
|
|
||||||
// Performant Parser of JSONs into Events
|
// Performant Parser of JSONs into Events
|
||||||
api 'com.fasterxml.jackson.module:jackson-module-kotlin:2.16.1'
|
api libs.jackson.module.kotlin
|
||||||
|
|
||||||
// immutable collections to avoid recomposition
|
// immutable collections to avoid recomposition
|
||||||
api('org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7')
|
api libs.kotlinx.collections.immutable
|
||||||
|
|
||||||
// Parses URLs from Text:
|
// Parses URLs from Text:
|
||||||
api "io.github.url-detector:url-detector:0.1.23"
|
api libs.url.detector
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation libs.junit
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
androidTestImplementation libs.androidx.junit
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
androidTestImplementation libs.androidx.espresso.core
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ class Lud06 {
|
|||||||
|
|
||||||
fun toLud16(str: String): String? {
|
fun toLud16(str: String): String? {
|
||||||
return try {
|
return try {
|
||||||
val url = toLnUrlp(str)
|
val url = toLnUrlp(str) ?: return null
|
||||||
|
|
||||||
val matcher = LNURLP_PATTERN.matcher(url)
|
val matcher = LNURLP_PATTERN.matcher(url)
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
|
|||||||
@@ -94,7 +94,9 @@ object Nip19Bech32 {
|
|||||||
val key = matcher.group(3) // bech32
|
val key = matcher.group(3) // bech32
|
||||||
val additionalChars = matcher.group(4) // additional chars
|
val additionalChars = matcher.group(4) // additional chars
|
||||||
|
|
||||||
return parseComponents(type!!, key, additionalChars)
|
if (type == null) return null
|
||||||
|
|
||||||
|
return parseComponents(type, key, additionalChars)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Log.e("NIP19 Parser", "Issue trying to Decode NIP19 $uri: ${e.message}", e)
|
Log.e("NIP19 Parser", "Issue trying to Decode NIP19 $uri: ${e.message}", e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,14 +105,16 @@ open class BaseTextNoteEvent(
|
|||||||
val additionalChars = matcher2.group(4) // additional chars
|
val additionalChars = matcher2.group(4) // additional chars
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val parsed = Nip19Bech32.parseComponents(type, key, additionalChars)?.entity
|
if (type != null) {
|
||||||
|
val parsed = Nip19Bech32.parseComponents(type, key, additionalChars)?.entity
|
||||||
|
|
||||||
if (parsed != null) {
|
if (parsed != null) {
|
||||||
if (parsed is Nip19Bech32.NProfile) {
|
if (parsed is Nip19Bech32.NProfile) {
|
||||||
returningList.add(parsed.hex)
|
returningList.add(parsed.hex)
|
||||||
}
|
}
|
||||||
if (parsed is Nip19Bech32.NPub) {
|
if (parsed is Nip19Bech32.NPub) {
|
||||||
returningList.add(parsed.hex)
|
returningList.add(parsed.hex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -151,14 +153,16 @@ open class BaseTextNoteEvent(
|
|||||||
val key = matcher2.group(3) // bech32
|
val key = matcher2.group(3) // bech32
|
||||||
val additionalChars = matcher2.group(4) // additional chars
|
val additionalChars = matcher2.group(4) // additional chars
|
||||||
|
|
||||||
val parsed = Nip19Bech32.parseComponents(type, key, additionalChars)?.entity
|
if (type != null) {
|
||||||
|
val parsed = Nip19Bech32.parseComponents(type, key, additionalChars)?.entity
|
||||||
|
|
||||||
if (parsed != null) {
|
if (parsed != null) {
|
||||||
when (parsed) {
|
when (parsed) {
|
||||||
is Nip19Bech32.NEvent -> citations.add(parsed.hex)
|
is Nip19Bech32.NEvent -> citations.add(parsed.hex)
|
||||||
is Nip19Bech32.NAddress -> citations.add(parsed.atag)
|
is Nip19Bech32.NAddress -> citations.add(parsed.atag)
|
||||||
is Nip19Bech32.Note -> citations.add(parsed.hex)
|
is Nip19Bech32.Note -> citations.add(parsed.hex)
|
||||||
is Nip19Bech32.NEmbed -> citations.add(parsed.event.id)
|
is Nip19Bech32.NEmbed -> citations.add(parsed.event.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -386,7 +386,7 @@ open class Event(
|
|||||||
put("pubkey", pubKey)
|
put("pubkey", pubKey)
|
||||||
put("created_at", createdAt)
|
put("created_at", createdAt)
|
||||||
put("kind", kind)
|
put("kind", kind)
|
||||||
put(
|
replace(
|
||||||
"tags",
|
"tags",
|
||||||
factory.arrayNode(tags.size).apply {
|
factory.arrayNode(tags.size).apply {
|
||||||
tags.forEach { tag ->
|
tags.forEach { tag ->
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ interface EventInterface {
|
|||||||
|
|
||||||
fun isTaggedUser(idHex: String): Boolean
|
fun isTaggedUser(idHex: String): Boolean
|
||||||
|
|
||||||
fun isTaggedUsers(idHex: Set<String>): Boolean
|
fun isTaggedUsers(idHexes: Set<String>): Boolean
|
||||||
|
|
||||||
fun isTaggedEvent(idHex: String): Boolean
|
fun isTaggedEvent(idHex: String): Boolean
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class GitPatchEvent(
|
|||||||
fun commitPGPSig() = tags.firstOrNull { it.size > 1 && it[0] == "commit-pgp-sig" }?.get(1)
|
fun commitPGPSig() = tags.firstOrNull { it.size > 1 && it[0] == "commit-pgp-sig" }?.get(1)
|
||||||
|
|
||||||
fun committer() =
|
fun committer() =
|
||||||
tags.filter { it.size > 1 && it[0] == "committer" }?.mapNotNull {
|
tags.filter { it.size > 1 && it[0] == "committer" }.mapNotNull {
|
||||||
Committer(it.getOrNull(1), it.getOrNull(2), it.getOrNull(3), it.getOrNull(4))
|
Committer(it.getOrNull(1), it.getOrNull(2), it.getOrNull(3), it.getOrNull(4))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ class TextNoteModificationEvent(
|
|||||||
content: String,
|
content: String,
|
||||||
sig: HexKey,
|
sig: HexKey,
|
||||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
|
fun editedNote() = firstTaggedEvent()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val KIND = 1010
|
const val KIND = 1010
|
||||||
const val ALT = "Content Change Event"
|
const val ALT = "Content Change Event"
|
||||||
|
|||||||
+10
-2
@@ -1,8 +1,14 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
gradlePluginPortal()
|
google {
|
||||||
google()
|
content {
|
||||||
|
includeGroupByRegex("com\\.android.*")
|
||||||
|
includeGroupByRegex("com\\.google.*")
|
||||||
|
includeGroupByRegex("androidx.*")
|
||||||
|
}
|
||||||
|
}
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
maven {
|
maven {
|
||||||
url "https://jitpack.io"
|
url "https://jitpack.io"
|
||||||
content {
|
content {
|
||||||
@@ -11,6 +17,7 @@ pluginManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
repositories {
|
repositories {
|
||||||
@@ -19,6 +26,7 @@ dependencyResolutionManagement {
|
|||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "Amethyst"
|
rootProject.name = "Amethyst"
|
||||||
include ':app'
|
include ':app'
|
||||||
include ':benchmark'
|
include ':benchmark'
|
||||||
|
|||||||
Reference in New Issue
Block a user