Merge branch 'main' into adding_translations

This commit is contained in:
Vitor Pamplona 2024-03-01 12:17:12 -05:00 committed by GitHub
commit 34150b6725
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 454 additions and 200 deletions

View File

@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.gms.google-services'
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid)
alias(libs.plugins.googleServices)
}
android {
@ -145,14 +145,13 @@ android {
// Should match compose version : https://developer.android.com/jetpack/androidx/releases/compose-kotlin
kotlinCompilerExtensionVersion "1.5.8"
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
excludes += ['/META-INF/{AL2.0,LGPL2.1}', '**/libscrypt.dylib']
}
exclude '**/libscrypt.dylib'
}
lint {
disable 'MissingTranslation'
}
@ -165,75 +164,78 @@ android {
dependencies {
implementation project(path: ':quartz')
implementation project(path: ':commons')
implementation "androidx.core:core-ktx:$core_ktx_version"
implementation 'androidx.activity:activity-compose:1.8.2'
implementation "androidx.compose.ui:ui:$compose_ui_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
implementation libs.androidx.core.ktx
implementation libs.androidx.activity.compose
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
implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation libs.androidx.fragment.ktx
// Navigation
implementation "androidx.navigation:navigation-compose:$nav_version"
implementation libs.androidx.navigation.compose
// Observe Live data as State
implementation "androidx.compose.runtime:runtime-livedata:$compose_ui_version"
implementation libs.androidx.runtime.livedata
// Material 3 Design
implementation "androidx.compose.material3:material3:${material3_version}"
implementation "androidx.compose.material:material-icons-extended:$compose_ui_version"
implementation libs.androidx.material3
implementation libs.androidx.material.icons
// Adaptive Layout / Two Pane
implementation "androidx.compose.material3:material3-window-size-class:${material3_version}"
implementation 'com.google.accompanist:accompanist-adaptive:0.34.0'
implementation libs.androidx.material3.windowSize
implementation libs.accompanist.adaptive
// Lifecycle
implementation "androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation libs.androidx.lifecycle.runtime.ktx
implementation libs.androidx.lifecycle.runtime.compose
implementation libs.androidx.lifecycle.viewmodel.compose
implementation libs.androidx.lifecycle.livedata.ktx
// Zoomable images
implementation 'net.engawapg.lib:zoomable:1.6.0'
implementation libs.zoomable
// Biometrics
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
implementation libs.androidx.biometric.ktx
// Websockets API
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.12'
implementation libs.okhttp
// HTML Parsing for Link Preview
implementation 'org.jsoup:jsoup:1.17.2'
implementation libs.jsoup
// Encrypted Key Storage
implementation 'androidx.security:security-crypto-ktx:1.1.0-alpha06'
implementation libs.androidx.security.crypto.ktx
// view videos
implementation "androidx.media3:media3-exoplayer:$media3_version"
implementation "androidx.media3:media3-exoplayer-hls:$media3_version"
implementation "androidx.media3:media3-ui:$media3_version"
implementation "androidx.media3:media3-session:$media3_version"
implementation libs.androidx.media3.exoplayer
implementation libs.androidx.media3.exoplayer.hls
implementation libs.androidx.media3.ui
implementation libs.androidx.media3.session
// important for proxy / tor
implementation "androidx.media3:media3-datasource-okhttp:$media3_version"
implementation libs.androidx.media3.datasource.okhttp
// Load images from the web.
implementation "io.coil-kt:coil-compose:$coil_version"
implementation libs.coil.compose
// view gifs
implementation "io.coil-kt:coil-gif:$coil_version"
implementation libs.coil.gif
// view svgs
implementation "io.coil-kt:coil-svg:$coil_version"
implementation libs.coil.svg
// create blurhash
implementation group: 'io.trbl', name: 'blurhash', version: '1.0.0'
implementation libs.trbl.blurhash
// Permission to upload pictures:
implementation "com.google.accompanist:accompanist-permissions:$accompanist_version"
implementation libs.accompanist.permissions
// For QR generation
implementation 'com.google.zxing:core:3.5.3'
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
implementation libs.zxing
implementation libs.zxing.embedded
// Markdown
//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"
// Markdown (With fix for full-image bleeds)
implementation('com.github.vitorpamplona.compose-richtext:richtext-ui:48702a8ced')
implementation('com.github.vitorpamplona.compose-richtext:richtext-ui-material3:48702a8ced')
implementation('com.github.vitorpamplona.compose-richtext:richtext-commonmark:48702a8ced')
implementation libs.markdown.ui
implementation libs.markdown.ui.material3
implementation libs.markdown.commonmark
// Language picker and Theme chooser
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation libs.androidx.appcompat
// 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
playImplementation 'com.google.mlkit:translate:17.0.2'
playImplementation libs.google.mlkit.translate
// PushNotifications
playImplementation platform('com.google.firebase:firebase-bom:32.7.2')
playImplementation 'com.google.firebase:firebase-messaging-ktx'
playImplementation platform(libs.firebase.bom)
playImplementation libs.firebase.messaging
//PushNotifications(FDroid)
fdroidImplementation 'com.github.UnifiedPush:android-connector:2.2.0'
fdroidImplementation libs.unifiedpush
// Charts
implementation "com.patrykandpatrick.vico:core:${vico_version}"
implementation "com.patrykandpatrick.vico:compose:${vico_version}"
implementation "com.patrykandpatrick.vico:views:${vico_version}"
implementation "com.patrykandpatrick.vico:compose-m2:${vico_version}"
implementation libs.vico.charts.core
implementation libs.vico.charts.compose
implementation libs.vico.charts.views
implementation libs.vico.charts.m3
// GeoHash
implementation 'com.github.drfonfon:android-kotlin-geohash:1.0'
implementation libs.drfonfon.geohash
// Waveform visualizer
implementation 'com.github.lincollincol:compose-audiowaveform:1.1.1'
implementation libs.audiowaveform
// Video compression lib
implementation 'com.github.AbedElazizShe:LightCompressor:1.3.2'
implementation libs.abedElazizShe.image.compressor
// Image compression lib
implementation 'id.zelory:compressor:3.0.1'
implementation libs.zelory.video.compressor
testImplementation 'junit:junit:4.13.2'
testImplementation 'io.mockk:mockk:1.13.9'
androidTestImplementation 'androidx.test.ext:junit:1.2.0-alpha03'
androidTestImplementation 'androidx.test.ext:junit-ktx:1.2.0-alpha03'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"
testImplementation libs.junit
testImplementation libs.mockk
androidTestImplementation libs.androidx.junit
androidTestImplementation libs.androidx.junit.ktx
androidTestImplementation libs.androidx.espresso.core
debugImplementation libs.androidx.ui.tooling
debugImplementation libs.androidx.ui.test.manifest
}
// https://gitlab.com/fdroid/wiki/-/wikis/HOWTO:-diff-&-fix-APKs-for-Reproducible-Builds#differing-assetsdexoptbaselineprofm-easy-to-fix

View File

@ -21,6 +21,7 @@
package com.vitorpamplona.amethyst.model
import android.util.Log
import android.util.LruCache
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.service.checkNotInMainThread
@ -1402,6 +1403,13 @@ object LocalCache {
note.loadEvent(event, author, emptyList())
event.editedNote()?.let {
getNoteIfExists(it)?.let { editedNote ->
modificationCache.remove(editedNote.idHex)
editedNote.liveSet?.innerModifications?.invalidateData()
}
}
refreshObservers(note)
}
@ -1716,15 +1724,31 @@ object LocalCache {
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> {
checkNotInMainThread()
modificationCache[note.idHex]?.let {
return it
}
val time = TimeUtils.now()
return noteListCache.filter { item ->
val noteEvent = item.event
val newNotes =
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() {

View File

@ -37,26 +37,26 @@ class JsonFilter(
val filter =
factory.objectNode().apply {
ids?.run {
put(
replace(
"ids",
factory.arrayNode(ids.size).apply { ids.forEach { add(it) } },
)
}
authors?.run {
put(
replace(
"authors",
factory.arrayNode(authors.size).apply { authors.forEach { add(it) } },
)
}
kinds?.run {
put(
replace(
"kinds",
factory.arrayNode(kinds.size).apply { kinds.forEach { add(it) } },
)
}
tags?.run {
entries.forEach { kv ->
put(
replace(
"#${kv.key}",
factory.arrayNode(kv.value.size).apply { kv.value.forEach { add(it) } },
)

View File

@ -47,7 +47,7 @@ data class Subscription(
return factory.objectNode().apply {
put("id", id)
typedFilters?.also { filters ->
put(
replace(
"typedFilters",
factory.arrayNode(filters.size).apply {
filters.forEach { filter -> add(filter.toJsonObject()) }

View File

@ -36,8 +36,8 @@ class TypedFilter(
val factory = Event.mapper.nodeFactory
return factory.objectNode().apply {
put("types", typesToJson(types))
put("filter", filterToJson(filter))
replace("types", typesToJson(types))
replace("filter", filterToJson(filter))
}
}
@ -50,26 +50,26 @@ class TypedFilter(
val factory = Event.mapper.nodeFactory
return factory.objectNode().apply {
filter.ids?.run {
put(
replace(
"ids",
factory.arrayNode(filter.ids.size).apply { filter.ids.forEach { add(it) } },
)
}
filter.authors?.run {
put(
replace(
"authors",
factory.arrayNode(filter.authors.size).apply { filter.authors.forEach { add(it) } },
)
}
filter.kinds?.run {
put(
replace(
"kinds",
factory.arrayNode(filter.kinds.size).apply { filter.kinds.forEach { add(it) } },
)
}
filter.tags?.run {
entries.forEach { kv ->
put(
replace(
"#${kv.key}",
factory.arrayNode(kv.value.size).apply { kv.value.forEach { add(it) } },
)

View File

@ -61,6 +61,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
@ -1088,10 +1089,58 @@ fun InnerNoteWithReactions(
}
@Stable
class EditState(
val showOriginal: MutableState<Boolean> = mutableStateOf(false),
val modificationsInOrder: MutableState<List<Note>> = mutableStateOf(emptyList()),
)
class EditState() {
private var modificationsList: List<Note> = persistentListOf()
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
private fun NoteBody(
@ -1105,14 +1154,7 @@ private fun NoteBody(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val editState by
produceState(initialValue = EditState(), key1 = baseNote) {
accountViewModel.findModificationEventsForNote(baseNote) { newModifications ->
if (value.modificationsInOrder.value != newModifications) {
value.modificationsInOrder.value = newModifications
}
}
}
val editState = observeEdits(baseNote = baseNote, accountViewModel = accountViewModel)
FirstUserInfoRow(
baseNote = baseNote,
@ -1168,7 +1210,7 @@ private fun RenderNoteRow(
backgroundColor: MutableState<Color>,
makeItShort: Boolean,
canPreview: Boolean,
editState: EditState,
editState: State<GenericLoadable<EditState>>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
@ -1358,7 +1400,7 @@ fun RenderTextEvent(
makeItShort: Boolean,
canPreview: Boolean,
backgroundColor: MutableState<Color>,
editState: EditState,
editState: State<GenericLoadable<EditState>>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
@ -1371,10 +1413,10 @@ fun RenderTextEvent(
derivedStateOf {
val subject = (note.event as? TextNoteEvent)?.subject()?.ifEmpty { null }
val newBody =
if (editState.showOriginal.value || editState.modificationsInOrder.value.isEmpty()) {
body
if (editState.value is GenericLoadable.Loaded) {
(editState.value as? GenericLoadable.Loaded)?.loaded?.modificationToShow?.value?.event?.content() ?: body
} else {
editState.modificationsInOrder.value.firstOrNull()?.event?.content() ?: body
body
}
if (!subject.isNullOrBlank() && !newBody.split("\n")[0].contains(subject)) {
@ -2822,7 +2864,7 @@ fun DisplayLocation(
fun FirstUserInfoRow(
baseNote: Note,
showAuthorPicture: Boolean,
editState: EditState,
editState: State<GenericLoadable<EditState>>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
@ -2857,8 +2899,10 @@ fun FirstUserInfoRow(
DisplayFollowingHashtagsInPost(baseNote, accountViewModel, nav)
}
if (!editState.modificationsInOrder.value.isEmpty()) {
DisplayEditStatus(editState.showOriginal)
if (editState.value is GenericLoadable.Loaded) {
(editState.value as? GenericLoadable.Loaded<EditState>)?.loaded?.let {
DisplayEditStatus(it)
}
}
TimeAgo(baseNote)
@ -2868,15 +2912,69 @@ fun FirstUserInfoRow(
}
@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(
text =
if (showOriginal.value) {
buildAnnotatedString { append(stringResource(id = R.string.original)) }
} else {
buildAnnotatedString { append(stringResource(id = R.string.edited)) }
buildAnnotatedString {
if (editState.showingVersion.value == editState.originalVersionId()) {
append(stringResource(id = R.string.original))
} 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 =
LocalTextStyle.current.copy(
color = MaterialTheme.colorScheme.placeholderText,

View File

@ -111,14 +111,17 @@ class PollNoteViewModel : ViewModel() {
}
tallies.forEach {
it.zappedValue.value = zappedPollOptionAmount(it.option)
it.tally.value =
if (totalZapped.compareTo(BigDecimal.ZERO) > 0) {
it.zappedValue.value.divide(totalZapped, 2, RoundingMode.HALF_UP)
val zappedValue = zappedPollOptionAmount(it.option)
val tallyValue =
if (totalZapped > BigDecimal.ZERO) {
zappedValue.divide(totalZapped, 2, RoundingMode.HALF_UP)
} else {
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
}
}

View File

@ -483,7 +483,10 @@ fun NoteDropDownMenu(
if (wantsToEditPost.value) {
EditPostView(
onClose = { wantsToEditPost.value = false },
onClose = {
popupExpanded.value = false
wantsToEditPost.value = false
},
edit = note,
accountViewModel = accountViewModel,
nav = nav,

View File

@ -58,7 +58,6 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
@ -86,6 +85,7 @@ import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
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.LoadNote
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.RenderTextEvent
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.timeAgo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@ -377,20 +378,13 @@ fun NoteMaster(
onClick = { showHiddenNote = true },
)
} else {
val editState by
produceState(initialValue = EditState(), key1 = baseNote) {
accountViewModel.findModificationEventsForNote(baseNote) { newModifications ->
if (value.modificationsInOrder.value != newModifications) {
value.modificationsInOrder.value = newModifications
}
}
}
Column(
modifier
.fillMaxWidth()
.padding(top = 10.dp),
) {
val editState = observeEdits(baseNote = baseNote, accountViewModel = accountViewModel)
Row(
modifier =
Modifier
@ -421,8 +415,10 @@ fun NoteMaster(
DisplayFollowingHashtagsInPost(baseNote, accountViewModel, nav)
}
if (!editState.modificationsInOrder.value.isEmpty()) {
DisplayEditStatus(editState.showOriginal)
if (editState.value is GenericLoadable.Loaded) {
(editState.value as? GenericLoadable.Loaded<EditState>)?.loaded?.let {
DisplayEditStatus(it)
}
}
Text(
@ -850,9 +846,9 @@ private fun RenderWikiHeaderForThreadPreview() {
val accountViewModel = mockAccountViewModel()
val nav: (String) -> Unit = {}
val editState by
val editState =
remember {
mutableStateOf(EditState())
mutableStateOf(GenericLoadable.Empty<EditState>())
}
runBlocking {

View File

@ -932,6 +932,8 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
}
}
fun cachedModificationEventsForNote(note: Note) = LocalCache.cachedModificationEventsForNote(note)
suspend fun findModificationEventsForNote(
note: Note,
onResult: (List<Note>) -> Unit,

View File

@ -51,6 +51,7 @@
<string name="boost">Boost</string>
<string name="boosted">boosted</string>
<string name="edited">edited</string>
<string name="edited_number">edit #%1$s</string>
<string name="original">original</string>
<string name="quote">Quote</string>
<string name="fork">Fork</string>

View File

@ -1,7 +1,7 @@
plugins {
id 'com.android.library'
id 'androidx.benchmark'
id 'org.jetbrains.kotlin.android'
alias(libs.plugins.androidLibrary)
alias(libs.plugins.jetbrainsKotlinAndroid)
alias(libs.plugins.androidBenchmark)
}
android {
@ -49,10 +49,10 @@ android {
}
dependencies {
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.benchmark:benchmark-junit4:1.2.3'
androidTestImplementation libs.androidx.runner
androidTestImplementation libs.androidx.junit
androidTestImplementation libs.junit
androidTestImplementation libs.androidx.benchmark.junit4
androidTestImplementation project(path: ':quartz')
androidTestImplementation project(path: ':commons')

View File

@ -1,31 +1,13 @@
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 {
id 'com.android.application' version '8.2.2' apply false
id 'com.android.library' version '8.2.2' apply false
id 'org.jetbrains.kotlin.android' version '1.9.22' apply false
id 'org.jetbrains.kotlin.jvm' version '1.9.22' apply false
id 'androidx.benchmark' version '1.2.3' apply false
id 'com.diffplug.spotless' version '6.25.0' apply false
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
alias(libs.plugins.androidLibrary) apply false
alias(libs.plugins.jetbrainsKotlinJvm) apply false
alias(libs.plugins.androidBenchmark) apply false
alias(libs.plugins.diffplugSpotless) apply false
alias(libs.plugins.googleServices) apply false
}
subprojects {

View File

@ -1,6 +1,6 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
alias(libs.plugins.androidLibrary)
alias(libs.plugins.jetbrainsKotlinAndroid)
}
android {
@ -38,12 +38,13 @@ dependencies {
implementation project(path: ':quartz')
// 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
api('org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7')
api libs.kotlinx.collections.immutable
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
testImplementation libs.junit
androidTestImplementation libs.androidx.junit
androidTestImplementation libs.androidx.espresso.core
}

124
gradle/libs.versions.toml Normal file
View File

@ -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" }

View File

@ -1,6 +1,6 @@
#Wed Jan 04 09:23:50 EST 2023
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
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@ -1,6 +1,6 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
alias(libs.plugins.androidLibrary)
alias(libs.plugins.jetbrainsKotlinAndroid)
}
android {
@ -33,36 +33,39 @@ android {
jvmTarget = '17'
freeCompilerArgs += "-Xstring-concat=inline"
}
packagingOptions {
// delete native scrypt lib because we cannot use dylibs in android
exclude '**/libscrypt.dylib'
resources {
excludes += ['**/libscrypt.dylib']
}
}
}
dependencies {
implementation "androidx.core:core-ktx:$core_ktx_version"
implementation libs.androidx.core.ktx
implementation platform(libs.androidx.compose.bom)
// @Immutable and @Stable
implementation "androidx.compose.runtime:runtime:$compose_ui_version"
implementation libs.androidx.runtime.runtime
// 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)
// Wait for @aar support in version catalogs
implementation "com.goterl:lazysodium-android:5.1.0@aar"
implementation 'net.java.dev.jna:jna:5.14.0@aar'
// 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
api('org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7')
api libs.kotlinx.collections.immutable
// Parses URLs from Text:
api "io.github.url-detector:url-detector:0.1.23"
api libs.url.detector
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
testImplementation libs.junit
androidTestImplementation libs.androidx.junit
androidTestImplementation libs.androidx.espresso.core
}

View File

@ -30,7 +30,7 @@ class Lud06 {
fun toLud16(str: String): String? {
return try {
val url = toLnUrlp(str)
val url = toLnUrlp(str) ?: return null
val matcher = LNURLP_PATTERN.matcher(url)
if (matcher.find()) {

View File

@ -94,7 +94,9 @@ object Nip19Bech32 {
val key = matcher.group(3) // bech32
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) {
Log.e("NIP19 Parser", "Issue trying to Decode NIP19 $uri: ${e.message}", e)
}

View File

@ -105,14 +105,16 @@ open class BaseTextNoteEvent(
val additionalChars = matcher2.group(4) // additional chars
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 is Nip19Bech32.NProfile) {
returningList.add(parsed.hex)
}
if (parsed is Nip19Bech32.NPub) {
returningList.add(parsed.hex)
if (parsed != null) {
if (parsed is Nip19Bech32.NProfile) {
returningList.add(parsed.hex)
}
if (parsed is Nip19Bech32.NPub) {
returningList.add(parsed.hex)
}
}
}
} catch (e: Exception) {
@ -151,14 +153,16 @@ open class BaseTextNoteEvent(
val key = matcher2.group(3) // bech32
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) {
when (parsed) {
is Nip19Bech32.NEvent -> citations.add(parsed.hex)
is Nip19Bech32.NAddress -> citations.add(parsed.atag)
is Nip19Bech32.Note -> citations.add(parsed.hex)
is Nip19Bech32.NEmbed -> citations.add(parsed.event.id)
if (parsed != null) {
when (parsed) {
is Nip19Bech32.NEvent -> citations.add(parsed.hex)
is Nip19Bech32.NAddress -> citations.add(parsed.atag)
is Nip19Bech32.Note -> citations.add(parsed.hex)
is Nip19Bech32.NEmbed -> citations.add(parsed.event.id)
}
}
}
}

View File

@ -386,7 +386,7 @@ open class Event(
put("pubkey", pubKey)
put("created_at", createdAt)
put("kind", kind)
put(
replace(
"tags",
factory.arrayNode(tags.size).apply {
tags.forEach { tag ->

View File

@ -63,7 +63,7 @@ interface EventInterface {
fun isTaggedUser(idHex: String): Boolean
fun isTaggedUsers(idHex: Set<String>): Boolean
fun isTaggedUsers(idHexes: Set<String>): Boolean
fun isTaggedEvent(idHex: String): Boolean

View File

@ -60,7 +60,7 @@ class GitPatchEvent(
fun commitPGPSig() = tags.firstOrNull { it.size > 1 && it[0] == "commit-pgp-sig" }?.get(1)
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))
}

View File

@ -34,6 +34,8 @@ class TextNoteModificationEvent(
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
fun editedNote() = firstTaggedEvent()
companion object {
const val KIND = 1010
const val ALT = "Content Change Event"

View File

@ -1,8 +1,14 @@
pluginManagement {
repositories {
gradlePluginPortal()
google()
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
maven {
url "https://jitpack.io"
content {
@ -11,6 +17,7 @@ pluginManagement {
}
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
@ -19,6 +26,7 @@ dependencyResolutionManagement {
maven { url "https://jitpack.io" }
}
}
rootProject.name = "Amethyst"
include ':app'
include ':benchmark'