kopia lustrzana https://github.com/vitorpamplona/amethyst
Porównaj commity
44 Commity
e7fc6e4efe
...
261481ecba
Autor | SHA1 | Data |
---|---|---|
Tony Giorgio | 261481ecba | |
Vitor Pamplona | 9be4895080 | |
Crowdin Bot | 6250db01d1 | |
Vitor Pamplona | 48f9045f1b | |
Vitor Pamplona | 818ca7e39e | |
Vitor Pamplona | e8675b8e45 | |
David Kaspar | cef7e17447 | |
greenart7c3 | 6b15a0db8e | |
greenart7c3 | 50c5845a11 | |
Vitor Pamplona | 1b7ba3de01 | |
Vitor Pamplona | 712063f5d2 | |
Vitor Pamplona | d92f23e274 | |
Vitor Pamplona | 3b7f530c0b | |
Crowdin Bot | 623a8d377c | |
Vitor Pamplona | 79489d0b07 | |
Vitor Pamplona | 827512b225 | |
Vitor Pamplona | 6acfadeb9b | |
Vitor Pamplona | e159af2cd7 | |
Vitor Pamplona | 89c2e9d2e0 | |
Vitor Pamplona | 06f6ab6719 | |
Vitor Pamplona | 98c48e8b6b | |
Vitor Pamplona | 25cde455d8 | |
Vitor Pamplona | ef0fdf553c | |
Vitor Pamplona | 719b950272 | |
Vitor Pamplona | 2d02fad6b9 | |
Crowdin Bot | a39db5bf7b | |
Vitor Pamplona | 7fd37367fc | |
Vitor Pamplona | e1c134830e | |
Vitor Pamplona | 621d1c7731 | |
Vitor Pamplona | 7475143506 | |
Vitor Pamplona | 2509d639bd | |
Crowdin Bot | 85dd5cf698 | |
Vitor Pamplona | 274ce6ad77 | |
Vitor Pamplona | 0e8d2fc33a | |
Vitor Pamplona | b88723b68b | |
Vitor Pamplona | 638dba770d | |
Vitor Pamplona | 4d7de6bc19 | |
Vitor Pamplona | fbf676bdb2 | |
Vitor Pamplona | 793780f02c | |
Crowdin Bot | adf31ed115 | |
Vitor Pamplona | 96e434bfcf | |
Vitor Pamplona | c7563c938d | |
Vitor Pamplona | 4380393c5b | |
Tony Giorgio | 08f1b43908 |
|
@ -84,7 +84,7 @@ jobs:
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: actions/create-release@v1
|
uses: actions/create-release@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ github.ref }}
|
tag_name: ${{ github.ref }}
|
||||||
release_name: Release ${{ github.ref }}
|
release_name: Release ${{ github.ref }}
|
||||||
|
@ -96,7 +96,7 @@ jobs:
|
||||||
id: upload-release-asset-play-universal-apk
|
id: upload-release-asset-play-universal-apk
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: app/build/outputs/apk/play/release/app-play-universal-release-unsigned-signed.apk
|
asset_path: app/build/outputs/apk/play/release/app-play-universal-release-unsigned-signed.apk
|
||||||
|
@ -107,7 +107,7 @@ jobs:
|
||||||
id: upload-release-asset-play-x86-apk
|
id: upload-release-asset-play-x86-apk
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: app/build/outputs/apk/play/release/app-play-x86-release-unsigned-signed.apk
|
asset_path: app/build/outputs/apk/play/release/app-play-x86-release-unsigned-signed.apk
|
||||||
|
@ -118,7 +118,7 @@ jobs:
|
||||||
id: upload-release-asset-play-x86-64-apk
|
id: upload-release-asset-play-x86-64-apk
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: app/build/outputs/apk/play/release/app-play-x86_64-release-unsigned-signed.apk
|
asset_path: app/build/outputs/apk/play/release/app-play-x86_64-release-unsigned-signed.apk
|
||||||
|
@ -152,7 +152,7 @@ jobs:
|
||||||
id: upload-release-asset-fdroid-universal-apk
|
id: upload-release-asset-fdroid-universal-apk
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: app/build/outputs/apk/fdroid/release/app-fdroid-universal-release-unsigned-signed.apk
|
asset_path: app/build/outputs/apk/fdroid/release/app-fdroid-universal-release-unsigned-signed.apk
|
||||||
|
@ -163,7 +163,7 @@ jobs:
|
||||||
id: upload-release-asset-fdroid-x86-apk
|
id: upload-release-asset-fdroid-x86-apk
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: app/build/outputs/apk/fdroid/release/app-fdroid-x86-release-unsigned-signed.apk
|
asset_path: app/build/outputs/apk/fdroid/release/app-fdroid-x86-release-unsigned-signed.apk
|
||||||
|
@ -174,7 +174,7 @@ jobs:
|
||||||
id: upload-release-asset-fdroid-x86-64-apk
|
id: upload-release-asset-fdroid-x86-64-apk
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: app/build/outputs/apk/fdroid/release/app-fdroid-x86_64-release-unsigned-signed.apk
|
asset_path: app/build/outputs/apk/fdroid/release/app-fdroid-x86_64-release-unsigned-signed.apk
|
||||||
|
@ -210,7 +210,7 @@ jobs:
|
||||||
id: upload-release-asset-play-aab
|
id: upload-release-asset-play-aab
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: app/build/outputs/bundle/playRelease/app-play-release.aab
|
asset_path: app/build/outputs/bundle/playRelease/app-play-release.aab
|
||||||
|
@ -222,7 +222,7 @@ jobs:
|
||||||
id: upload-release-asset-fdroid-aab
|
id: upload-release-asset-fdroid-aab
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: app/build/outputs/bundle/fdroidRelease/app-fdroid-release.aab
|
asset_path: app/build/outputs/bundle/fdroidRelease/app-fdroid-release.aab
|
||||||
|
|
|
@ -12,9 +12,9 @@ android {
|
||||||
applicationId "com.vitorpamplona.amethyst"
|
applicationId "com.vitorpamplona.amethyst"
|
||||||
minSdk 26
|
minSdk 26
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 362
|
versionCode 364
|
||||||
versionName "0.85.3"
|
versionName "0.86.1"
|
||||||
buildConfigField "String", "RELEASE_NOTES_ID", "\"d8da33fd13d129d86c53564aedefafbe3716f007c520431be4a8e488d3925afb\""
|
buildConfigField "String", "RELEASE_NOTES_ID", "\"a704a11334ed4fe6fc6ee6f8856f6f005da33644770616f1437f8b2b488b52b1\""
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|
|
@ -108,11 +108,15 @@ import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combineTransform
|
import kotlinx.coroutines.flow.combineTransform
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flattenMerge
|
import kotlinx.coroutines.flow.flattenMerge
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.transformLatest
|
import kotlinx.coroutines.flow.transformLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -216,179 +220,114 @@ class Account(
|
||||||
val communities: ImmutableSet<String> = persistentSetOf(),
|
val communities: ImmutableSet<String> = persistentSetOf(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class ListNameNotePair(val listName: String, val event: GeneralListEvent?)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
val liveKind3Follows: StateFlow<LiveFollowLists> by lazy {
|
val liveKind3FollowsFlow: Flow<LiveFollowLists> =
|
||||||
userProfile()
|
userProfile().flow().follows.stateFlow.transformLatest {
|
||||||
.live()
|
emit(
|
||||||
.follows
|
LiveFollowLists(
|
||||||
.asFlow()
|
it.user.cachedFollowingKeySet().toImmutableSet(),
|
||||||
.transformLatest {
|
it.user.cachedFollowingTagSet().toImmutableSet(),
|
||||||
emit(
|
it.user.cachedFollowingGeohashSet().toImmutableSet(),
|
||||||
LiveFollowLists(
|
it.user.cachedFollowingCommunitiesSet().toImmutableSet(),
|
||||||
userProfile().cachedFollowingKeySet().toImmutableSet(),
|
),
|
||||||
userProfile().cachedFollowingTagSet().toImmutableSet(),
|
)
|
||||||
userProfile().cachedFollowingGeohashSet().toImmutableSet(),
|
}
|
||||||
userProfile().cachedFollowingCommunitiesSet().toImmutableSet(),
|
|
||||||
),
|
val liveKind3Follows = liveKind3FollowsFlow.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
||||||
)
|
|
||||||
}
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
private val liveHomeList: Flow<ListNameNotePair> by lazy {
|
||||||
|
defaultHomeFollowList.flatMapLatest { listName ->
|
||||||
|
loadPeopleListFlowFromListName(listName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private val liveHomeList: StateFlow<NoteState?> by lazy {
|
fun loadPeopleListFlowFromListName(listName: String): Flow<ListNameNotePair> {
|
||||||
defaultHomeFollowList
|
return if (listName != GLOBAL_FOLLOWS && listName != KIND3_FOLLOWS) {
|
||||||
.transformLatest {
|
val note = LocalCache.checkGetOrCreateAddressableNote(listName)
|
||||||
if (it != GLOBAL_FOLLOWS && it != KIND3_FOLLOWS) {
|
note?.flow()?.metadata?.stateFlow?.mapLatest {
|
||||||
LocalCache.checkGetOrCreateAddressableNote(it)?.flow()?.metadata?.stateFlow?.let {
|
val noteEvent = it.note.event as? GeneralListEvent
|
||||||
emit(it)
|
ListNameNotePair(listName, noteEvent)
|
||||||
}
|
} ?: MutableStateFlow(ListNameNotePair(listName, null))
|
||||||
|
} else {
|
||||||
|
MutableStateFlow(ListNameNotePair(listName, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun combinePeopleListFlows(
|
||||||
|
kind3FollowsSource: Flow<LiveFollowLists>,
|
||||||
|
peopleListFollowsSource: Flow<ListNameNotePair>,
|
||||||
|
): Flow<LiveFollowLists?> {
|
||||||
|
return combineTransform(kind3FollowsSource, peopleListFollowsSource) { kind3Follows, peopleListFollows ->
|
||||||
|
if (peopleListFollows.listName == GLOBAL_FOLLOWS) {
|
||||||
|
emit(null)
|
||||||
|
} else if (peopleListFollows.listName == KIND3_FOLLOWS) {
|
||||||
|
emit(kind3Follows)
|
||||||
|
} else if (peopleListFollows.event == null) {
|
||||||
|
emit(LiveFollowLists())
|
||||||
|
} else {
|
||||||
|
val result = waitToDecrypt(peopleListFollows.event)
|
||||||
|
if (result == null) {
|
||||||
|
emit(LiveFollowLists())
|
||||||
|
} else {
|
||||||
|
emit(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.flattenMerge()
|
}
|
||||||
.stateIn(scope, SharingStarted.Eagerly, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val liveHomeFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
val liveHomeFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
||||||
combineTransform(defaultHomeFollowList, liveKind3Follows, liveHomeList) {
|
combinePeopleListFlows(liveKind3FollowsFlow, liveHomeList)
|
||||||
listName,
|
|
||||||
kind3Follows,
|
|
||||||
peopleListFollows,
|
|
||||||
->
|
|
||||||
if (listName == GLOBAL_FOLLOWS) {
|
|
||||||
emit(null)
|
|
||||||
} else if (listName == KIND3_FOLLOWS) {
|
|
||||||
emit(kind3Follows)
|
|
||||||
} else {
|
|
||||||
val result =
|
|
||||||
withTimeoutOrNull(1000) {
|
|
||||||
suspendCancellableCoroutine { continuation ->
|
|
||||||
decryptLiveFollows(peopleListFollows) { continuation.resume(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result?.let { emit(it) } ?: run { emit(LiveFollowLists()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private val liveNotificationList: StateFlow<NoteState?> by lazy {
|
private val liveNotificationList: Flow<ListNameNotePair> by lazy {
|
||||||
defaultNotificationFollowList
|
defaultNotificationFollowList
|
||||||
.transformLatest {
|
.transformLatest { listName ->
|
||||||
if (it != GLOBAL_FOLLOWS && it != KIND3_FOLLOWS) {
|
emit(loadPeopleListFlowFromListName(listName))
|
||||||
LocalCache.checkGetOrCreateAddressableNote(it)?.flow()?.metadata?.stateFlow?.let {
|
}.flattenMerge()
|
||||||
emit(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.flattenMerge()
|
|
||||||
.stateIn(scope, SharingStarted.Eagerly, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val liveNotificationFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
val liveNotificationFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
||||||
combineTransform(defaultNotificationFollowList, liveKind3Follows, liveNotificationList) {
|
combinePeopleListFlows(liveKind3FollowsFlow, liveNotificationList)
|
||||||
listName,
|
|
||||||
kind3Follows,
|
|
||||||
peopleListFollows,
|
|
||||||
->
|
|
||||||
if (listName == GLOBAL_FOLLOWS) {
|
|
||||||
emit(null)
|
|
||||||
} else if (listName == KIND3_FOLLOWS) {
|
|
||||||
emit(kind3Follows)
|
|
||||||
} else {
|
|
||||||
val result =
|
|
||||||
withTimeoutOrNull(1000) {
|
|
||||||
suspendCancellableCoroutine { continuation ->
|
|
||||||
decryptLiveFollows(peopleListFollows) { continuation.resume(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result?.let { emit(it) } ?: run { emit(LiveFollowLists()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private val liveStoriesList: StateFlow<NoteState?> by lazy {
|
private val liveStoriesList: Flow<ListNameNotePair> by lazy {
|
||||||
defaultStoriesFollowList
|
defaultStoriesFollowList
|
||||||
.transformLatest {
|
.transformLatest { listName ->
|
||||||
if (it != GLOBAL_FOLLOWS && it != KIND3_FOLLOWS) {
|
emit(loadPeopleListFlowFromListName(listName))
|
||||||
LocalCache.checkGetOrCreateAddressableNote(it)?.flow()?.metadata?.stateFlow?.let {
|
}.flattenMerge()
|
||||||
emit(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.flattenMerge()
|
|
||||||
.stateIn(scope, SharingStarted.Eagerly, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val liveStoriesFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
val liveStoriesFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
||||||
combineTransform(defaultStoriesFollowList, liveKind3Follows, liveStoriesList) {
|
combinePeopleListFlows(liveKind3FollowsFlow, liveStoriesList)
|
||||||
listName,
|
|
||||||
kind3Follows,
|
|
||||||
peopleListFollows,
|
|
||||||
->
|
|
||||||
if (listName == GLOBAL_FOLLOWS) {
|
|
||||||
emit(null)
|
|
||||||
} else if (listName == KIND3_FOLLOWS) {
|
|
||||||
emit(kind3Follows)
|
|
||||||
} else {
|
|
||||||
val result =
|
|
||||||
withTimeoutOrNull(1000) {
|
|
||||||
suspendCancellableCoroutine { continuation ->
|
|
||||||
decryptLiveFollows(peopleListFollows) { continuation.resume(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result?.let { emit(it) } ?: run { emit(LiveFollowLists()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private val liveDiscoveryList: StateFlow<NoteState?> by lazy {
|
private val liveDiscoveryList: Flow<ListNameNotePair> by lazy {
|
||||||
defaultDiscoveryFollowList
|
defaultDiscoveryFollowList
|
||||||
.transformLatest {
|
.transformLatest { listName ->
|
||||||
if (it != GLOBAL_FOLLOWS && it != KIND3_FOLLOWS) {
|
emit(loadPeopleListFlowFromListName(listName))
|
||||||
LocalCache.checkGetOrCreateAddressableNote(it)?.flow()?.metadata?.stateFlow?.let {
|
}.flattenMerge()
|
||||||
emit(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.flattenMerge()
|
|
||||||
.stateIn(scope, SharingStarted.Eagerly, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val liveDiscoveryFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
val liveDiscoveryFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
||||||
combineTransform(defaultDiscoveryFollowList, liveKind3Follows, liveDiscoveryList) {
|
combinePeopleListFlows(liveKind3FollowsFlow, liveDiscoveryList)
|
||||||
listName,
|
|
||||||
kind3Follows,
|
|
||||||
peopleListFollows,
|
|
||||||
->
|
|
||||||
if (listName == GLOBAL_FOLLOWS) {
|
|
||||||
emit(null)
|
|
||||||
} else if (listName == KIND3_FOLLOWS) {
|
|
||||||
emit(kind3Follows)
|
|
||||||
} else {
|
|
||||||
val result =
|
|
||||||
withTimeoutOrNull(1000) {
|
|
||||||
suspendCancellableCoroutine { continuation ->
|
|
||||||
decryptLiveFollows(peopleListFollows) { continuation.resume(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result?.let { emit(it) } ?: run { emit(LiveFollowLists()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptLiveFollows(
|
private fun decryptLiveFollows(
|
||||||
peopleListFollows: NoteState?,
|
listEvent: GeneralListEvent,
|
||||||
onReady: (LiveFollowLists) -> Unit,
|
onReady: (LiveFollowLists) -> Unit,
|
||||||
) {
|
) {
|
||||||
val listEvent = (peopleListFollows?.note?.event as? GeneralListEvent)
|
listEvent.privateTags(signer) { privateTagList ->
|
||||||
listEvent?.privateTags(signer) { privateTagList ->
|
|
||||||
onReady(
|
onReady(
|
||||||
LiveFollowLists(
|
LiveFollowLists(
|
||||||
users =
|
users =
|
||||||
|
@ -406,6 +345,16 @@ class Account(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun waitToDecrypt(peopleListFollows: GeneralListEvent): LiveFollowLists? {
|
||||||
|
return withTimeoutOrNull(1000) {
|
||||||
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
decryptLiveFollows(peopleListFollows) {
|
||||||
|
continuation.resume(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class LiveHiddenUsers(
|
data class LiveHiddenUsers(
|
||||||
val hiddenUsers: ImmutableSet<String>,
|
val hiddenUsers: ImmutableSet<String>,
|
||||||
|
@ -2353,7 +2302,7 @@ class Account(
|
||||||
} else if (event is LnZapRequestEvent && event.isPrivateZap() && isWriteable()) {
|
} else if (event is LnZapRequestEvent && event.isPrivateZap() && isWriteable()) {
|
||||||
event.cachedPrivateZap()?.content
|
event.cachedPrivateZap()?.content
|
||||||
} else {
|
} else {
|
||||||
event?.content()
|
event.content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2306,6 +2306,10 @@ object LocalCache {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasConsumed(notificationEvent: Event): Boolean {
|
||||||
|
return notes.containsKey(notificationEvent.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
|
|
@ -26,8 +26,6 @@ import com.vitorpamplona.amethyst.service.previews.BahaUrlPreview
|
||||||
import com.vitorpamplona.amethyst.service.previews.IUrlPreviewCallback
|
import com.vitorpamplona.amethyst.service.previews.IUrlPreviewCallback
|
||||||
import com.vitorpamplona.amethyst.service.previews.UrlInfoItem
|
import com.vitorpamplona.amethyst.service.previews.UrlInfoItem
|
||||||
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
object UrlCachedPreviewer {
|
object UrlCachedPreviewer {
|
||||||
|
@ -37,46 +35,44 @@ object UrlCachedPreviewer {
|
||||||
suspend fun previewInfo(
|
suspend fun previewInfo(
|
||||||
url: String,
|
url: String,
|
||||||
onReady: suspend (UrlPreviewState) -> Unit,
|
onReady: suspend (UrlPreviewState) -> Unit,
|
||||||
) = withContext(Dispatchers.IO) {
|
) {
|
||||||
cache[url]?.let {
|
cache[url]?.let {
|
||||||
onReady(it)
|
onReady(it)
|
||||||
return@withContext
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
BahaUrlPreview(
|
BahaUrlPreview(
|
||||||
url,
|
url,
|
||||||
object : IUrlPreviewCallback {
|
object : IUrlPreviewCallback {
|
||||||
override suspend fun onComplete(urlInfo: UrlInfoItem) =
|
override suspend fun onComplete(urlInfo: UrlInfoItem) {
|
||||||
withContext(Dispatchers.IO) {
|
cache[url]?.let {
|
||||||
cache[url]?.let {
|
if (it is UrlPreviewState.Loaded || it is UrlPreviewState.Empty) {
|
||||||
if (it is UrlPreviewState.Loaded || it is UrlPreviewState.Empty) {
|
|
||||||
onReady(it)
|
|
||||||
return@withContext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val state =
|
|
||||||
if (urlInfo.fetchComplete() && urlInfo.url == url) {
|
|
||||||
UrlPreviewState.Loaded(urlInfo)
|
|
||||||
} else {
|
|
||||||
UrlPreviewState.Empty
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.put(url, state)
|
|
||||||
onReady(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun onFailed(throwable: Throwable) =
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
cache[url]?.let {
|
|
||||||
onReady(it)
|
onReady(it)
|
||||||
return@withContext
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val state =
|
||||||
|
if (urlInfo.fetchComplete() && urlInfo.url == url) {
|
||||||
|
UrlPreviewState.Loaded(urlInfo)
|
||||||
|
} else {
|
||||||
|
UrlPreviewState.Empty
|
||||||
}
|
}
|
||||||
|
|
||||||
val state = UrlPreviewState.Error(throwable.message ?: "Error Loading url preview")
|
cache.put(url, state)
|
||||||
cache.put(url, state)
|
onReady(state)
|
||||||
onReady(state)
|
}
|
||||||
|
|
||||||
|
override suspend fun onFailed(throwable: Throwable) {
|
||||||
|
cache[url]?.let {
|
||||||
|
onReady(it)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val state = UrlPreviewState.Error(throwable.message ?: "Error Loading url preview")
|
||||||
|
cache.put(url, state)
|
||||||
|
onReady(state)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.fetchUrlPreview()
|
.fetchUrlPreview()
|
||||||
|
|
|
@ -127,6 +127,7 @@ class User(val pubkeyHex: String) {
|
||||||
|
|
||||||
// Update following of the current user
|
// Update following of the current user
|
||||||
liveSet?.innerFollows?.invalidateData()
|
liveSet?.innerFollows?.invalidateData()
|
||||||
|
flowSet?.follows?.invalidateData()
|
||||||
|
|
||||||
// Update Followers of the past user list
|
// Update Followers of the past user list
|
||||||
// Update Followers of the new contact list
|
// Update Followers of the new contact list
|
||||||
|
@ -474,14 +475,16 @@ class User(val pubkeyHex: String) {
|
||||||
@Stable
|
@Stable
|
||||||
class UserFlowSet(u: User) {
|
class UserFlowSet(u: User) {
|
||||||
// Observers line up here.
|
// Observers line up here.
|
||||||
|
val follows = UserBundledRefresherFlow(u)
|
||||||
val relays = UserBundledRefresherFlow(u)
|
val relays = UserBundledRefresherFlow(u)
|
||||||
|
|
||||||
fun isInUse(): Boolean {
|
fun isInUse(): Boolean {
|
||||||
return relays.stateFlow.subscriptionCount.value > 0
|
return relays.stateFlow.subscriptionCount.value > 0 || follows.stateFlow.subscriptionCount.value > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
relays.destroy()
|
relays.destroy()
|
||||||
|
follows.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,15 +64,17 @@ class EventNotificationConsumer(private val applicationContext: Context) {
|
||||||
account: Account,
|
account: Account,
|
||||||
) {
|
) {
|
||||||
pushWrappedEvent.cachedGift(account.signer) { notificationEvent ->
|
pushWrappedEvent.cachedGift(account.signer) { notificationEvent ->
|
||||||
LocalCache.justConsume(notificationEvent, null)
|
if (!LocalCache.hasConsumed(notificationEvent) && LocalCache.justVerify(notificationEvent)) {
|
||||||
|
unwrapAndConsume(notificationEvent, account) { innerEvent ->
|
||||||
unwrapAndConsume(notificationEvent, account) { innerEvent ->
|
if (!LocalCache.hasConsumed(innerEvent)) {
|
||||||
if (innerEvent is PrivateDmEvent) {
|
if (innerEvent is PrivateDmEvent) {
|
||||||
notify(innerEvent, account)
|
notify(innerEvent, account)
|
||||||
} else if (innerEvent is LnZapEvent) {
|
} else if (innerEvent is LnZapEvent) {
|
||||||
notify(innerEvent, account)
|
notify(innerEvent, account)
|
||||||
} else if (innerEvent is ChatMessageEvent) {
|
} else if (innerEvent is ChatMessageEvent) {
|
||||||
notify(innerEvent, account)
|
notify(innerEvent, account)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,18 +21,14 @@
|
||||||
package com.vitorpamplona.amethyst.service.previews
|
package com.vitorpamplona.amethyst.service.previews
|
||||||
|
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
class BahaUrlPreview(val url: String, var callback: IUrlPreviewCallback?) {
|
class BahaUrlPreview(val url: String, var callback: IUrlPreviewCallback?) {
|
||||||
suspend fun fetchUrlPreview(timeOut: Int = 30000) =
|
suspend fun fetchUrlPreview(timeOut: Int = 30000) =
|
||||||
withContext(Dispatchers.IO) {
|
try {
|
||||||
try {
|
fetch(timeOut)
|
||||||
fetch(timeOut)
|
} catch (t: Throwable) {
|
||||||
} catch (t: Throwable) {
|
if (t is CancellationException) throw t
|
||||||
if (t is CancellationException) throw t
|
callback?.onFailed(t)
|
||||||
callback?.onFailed(t)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetch(timeOut: Int = 30000) {
|
private suspend fun fetch(timeOut: Int = 30000) {
|
||||||
|
|
|
@ -353,9 +353,14 @@ class Relay(
|
||||||
if (read) {
|
if (read) {
|
||||||
if (isConnected()) {
|
if (isConnected()) {
|
||||||
if (isReady) {
|
if (isReady) {
|
||||||
if (filters.isNotEmpty()) {
|
val relayFilters =
|
||||||
|
filters.filter { filter ->
|
||||||
|
activeTypes.any { it in filter.types }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relayFilters.isNotEmpty()) {
|
||||||
val request =
|
val request =
|
||||||
filters.joinToStringLimited(
|
relayFilters.joinToStringLimited(
|
||||||
separator = ",",
|
separator = ",",
|
||||||
limit = 20,
|
limit = 20,
|
||||||
prefix = """["REQ","$requestId",""",
|
prefix = """["REQ","$requestId",""",
|
||||||
|
@ -423,12 +428,7 @@ class Relay(
|
||||||
fun renewFilters() {
|
fun renewFilters() {
|
||||||
// Force update all filters after AUTH.
|
// Force update all filters after AUTH.
|
||||||
Client.allSubscriptions().forEach {
|
Client.allSubscriptions().forEach {
|
||||||
val filters =
|
sendFilter(requestId = it.key, it.value)
|
||||||
it.value.filter { filter ->
|
|
||||||
activeTypes.any { it in filter.types }
|
|
||||||
}
|
|
||||||
|
|
||||||
sendFilter(requestId = it.key, filters)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,9 @@ object RelayPool : Relay.Listener {
|
||||||
subscriptionId: String,
|
subscriptionId: String,
|
||||||
filters: List<TypedFilter>,
|
filters: List<TypedFilter>,
|
||||||
) {
|
) {
|
||||||
relays.forEach { it.sendFilter(subscriptionId, filters) }
|
relays.forEach { relay ->
|
||||||
|
relay.sendFilter(subscriptionId, filters)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun connectAndSendFiltersIfDisconnected() {
|
fun connectAndSendFiltersIfDisconnected() {
|
||||||
|
|
|
@ -235,7 +235,12 @@ fun NewPostView(
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = { onClose() },
|
onDismissRequest = {
|
||||||
|
scope.launch {
|
||||||
|
postViewModel.sendDraftSync(relayList = relayList)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
},
|
||||||
properties =
|
properties =
|
||||||
DialogProperties(
|
DialogProperties(
|
||||||
usePlatformDefaultWidth = false,
|
usePlatformDefaultWidth = false,
|
||||||
|
@ -294,8 +299,9 @@ fun NewPostView(
|
||||||
Spacer(modifier = StdHorzSpacer)
|
Spacer(modifier = StdHorzSpacer)
|
||||||
CloseButton(
|
CloseButton(
|
||||||
onPress = {
|
onPress = {
|
||||||
postViewModel.cancel()
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
postViewModel.sendDraftSync(relayList = relayList)
|
||||||
|
postViewModel.cancel()
|
||||||
delay(100)
|
delay(100)
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
@ -1096,8 +1102,12 @@ fun FowardZapTo(
|
||||||
text = stringResource(R.string.zap_split_title),
|
text = stringResource(R.string.zap_split_title),
|
||||||
fontSize = 20.sp,
|
fontSize = 20.sp,
|
||||||
fontWeight = FontWeight.W500,
|
fontWeight = FontWeight.W500,
|
||||||
modifier = Modifier.padding(start = 10.dp),
|
modifier = Modifier.padding(horizontal = 10.dp).weight(1f),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
OutlinedButton(onClick = { postViewModel.updateZapFromText() }) {
|
||||||
|
Text(text = stringResource(R.string.load_from_text))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider(thickness = DividerThickness)
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
|
@ -1133,7 +1143,7 @@ fun FowardZapTo(
|
||||||
Slider(
|
Slider(
|
||||||
value = splitItem.percentage,
|
value = splitItem.percentage,
|
||||||
onValueChange = { sliderValue ->
|
onValueChange = { sliderValue ->
|
||||||
val rounded = (round(sliderValue * 20)) / 20.0f
|
val rounded = (round(sliderValue * 100)) / 100.0f
|
||||||
postViewModel.updateZapPercentage(index, rounded)
|
postViewModel.updateZapPercentage(index, rounded)
|
||||||
},
|
},
|
||||||
modifier = Modifier.weight(1.5f),
|
modifier = Modifier.weight(1.5f),
|
||||||
|
|
|
@ -77,6 +77,7 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
enum class UserSuggestionAnchor {
|
enum class UserSuggestionAnchor {
|
||||||
|
@ -200,12 +201,16 @@ open class NewPostViewModel() : ViewModel() {
|
||||||
val noteAuthor = draft?.author
|
val noteAuthor = draft?.author
|
||||||
|
|
||||||
if (draft != null && noteEvent is DraftEvent && noteAuthor != null) {
|
if (draft != null && noteEvent is DraftEvent && noteAuthor != null) {
|
||||||
accountViewModel.createTempDraftNote(noteEvent, noteAuthor) { innerNote ->
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val oldTag = (draft.event as? AddressableEvent)?.dTag()
|
accountViewModel.createTempDraftNote(noteEvent) { innerNote ->
|
||||||
if (oldTag != null) {
|
if (innerNote != null) {
|
||||||
draftTag = oldTag
|
val oldTag = (draft.event as? AddressableEvent)?.dTag()
|
||||||
|
if (oldTag != null) {
|
||||||
|
draftTag = oldTag
|
||||||
|
}
|
||||||
|
loadFromDraft(innerNote, accountViewModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
loadFromDraft(innerNote, accountViewModel)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
originalNote = replyingTo
|
originalNote = replyingTo
|
||||||
|
@ -442,18 +447,22 @@ open class NewPostViewModel() : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendDraft(relayList: List<Relay>? = null) {
|
fun sendDraft(relayList: List<Relay>? = null) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch {
|
||||||
innerSendPost(relayList, draftTag)
|
sendDraftSync(relayList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun sendDraftSync(relayList: List<Relay>? = null) {
|
||||||
|
innerSendPost(relayList, draftTag)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun innerSendPost(
|
private suspend fun innerSendPost(
|
||||||
relayList: List<Relay>? = null,
|
relayList: List<Relay>? = null,
|
||||||
localDraft: String?,
|
localDraft: String?,
|
||||||
) {
|
) = withContext(Dispatchers.IO) {
|
||||||
if (accountViewModel == null) {
|
if (accountViewModel == null) {
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return@withContext
|
||||||
}
|
}
|
||||||
|
|
||||||
val tagger = NewMessageTagger(message.text, pTags, eTags, originalNote?.channelHex(), accountViewModel!!)
|
val tagger = NewMessageTagger(message.text, pTags, eTags, originalNote?.channelHex(), accountViewModel!!)
|
||||||
|
@ -919,6 +928,7 @@ open class NewPostViewModel() : ViewModel() {
|
||||||
compareBy(
|
compareBy(
|
||||||
{ account?.isFollowing(it) },
|
{ account?.isFollowing(it) },
|
||||||
{ it.toBestDisplayName() },
|
{ it.toBestDisplayName() },
|
||||||
|
{ it.pubkeyHex },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.reversed()
|
.reversed()
|
||||||
|
@ -928,7 +938,6 @@ open class NewPostViewModel() : ViewModel() {
|
||||||
userSuggestions = emptyList()
|
userSuggestions = emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
saveDraft()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun autocompleteWithUser(item: User) {
|
open fun autocompleteWithUser(item: User) {
|
||||||
|
@ -947,16 +956,6 @@ open class NewPostViewModel() : ViewModel() {
|
||||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
|
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
|
||||||
forwardZapTo.addItem(item)
|
forwardZapTo.addItem(item)
|
||||||
forwardZapToEditting = TextFieldValue("")
|
forwardZapToEditting = TextFieldValue("")
|
||||||
/*
|
|
||||||
val lastWord = forwardZapToEditting.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
|
|
||||||
val lastWordStart = it.end - lastWord.length
|
|
||||||
val wordToInsert = "@${item.pubkeyNpub()}"
|
|
||||||
forwardZapTo = item
|
|
||||||
|
|
||||||
forwardZapToEditting = TextFieldValue(
|
|
||||||
forwardZapToEditting.text.replaceRange(lastWordStart, it.end, wordToInsert),
|
|
||||||
TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length)
|
|
||||||
)*/
|
|
||||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.TO_USERS) {
|
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.TO_USERS) {
|
||||||
val lastWord =
|
val lastWord =
|
||||||
toUsers.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
|
toUsers.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
|
||||||
|
@ -1199,6 +1198,18 @@ open class NewPostViewModel() : ViewModel() {
|
||||||
forwardZapTo.updatePercentage(index, sliderValue)
|
forwardZapTo.updatePercentage(index, sliderValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateZapFromText() {
|
||||||
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
val tagger = NewMessageTagger(message.text, emptyList(), emptyList(), null, accountViewModel!!)
|
||||||
|
tagger.run()
|
||||||
|
tagger.pTags?.forEach { taggedUser ->
|
||||||
|
if (!forwardZapTo.items.any { it.key == taggedUser }) {
|
||||||
|
forwardZapTo.addItem(taggedUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun updateZapRaiserAmount(newAmount: Long?) {
|
fun updateZapRaiserAmount(newAmount: Long?) {
|
||||||
zapRaiserAmount = newAmount
|
zapRaiserAmount = newAmount
|
||||||
saveDraft()
|
saveDraft()
|
||||||
|
|
|
@ -900,7 +900,15 @@ fun EditableServerConfig(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (url.isNotBlank() && url != "/") {
|
if (url.isNotBlank() && url != "/") {
|
||||||
var addedWSS =
|
var addedWSS =
|
||||||
if (!url.startsWith("wss://") && !url.startsWith("ws://")) "wss://$url" else url
|
if (!url.startsWith("wss://") && !url.startsWith("ws://")) {
|
||||||
|
if (url.endsWith(".onion") || url.endsWith(".onion/")) {
|
||||||
|
"ws://$url"
|
||||||
|
} else {
|
||||||
|
"wss://$url"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
url
|
||||||
|
}
|
||||||
if (url.endsWith("/")) addedWSS = addedWSS.dropLast(1)
|
if (url.endsWith("/")) addedWSS = addedWSS.dropLast(1)
|
||||||
onNewRelay(RelaySetupInfo(addedWSS, read, write, feedTypes = FeedType.values().toSet()))
|
onNewRelay(RelaySetupInfo(addedWSS, read, write, feedTypes = FeedType.values().toSet()))
|
||||||
url = ""
|
url = ""
|
||||||
|
|
|
@ -53,7 +53,7 @@ import com.vitorpamplona.amethyst.ui.theme.secondaryButtonBackground
|
||||||
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||||
|
|
||||||
object ShowFullTextCache {
|
object ShowFullTextCache {
|
||||||
val cache = LruCache<String, Boolean>(20)
|
val cache = LruCache<String, Boolean>(10)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
@ -501,8 +501,6 @@ private fun AddedImageFeatures(
|
||||||
ImageUrlWithDownloadButton(content.url, showImage)
|
ImageUrlWithDownloadButton(content.url, showImage)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var verifiedHash by remember(content.url) { mutableStateOf<Boolean?>(null) }
|
|
||||||
|
|
||||||
when (painter.value) {
|
when (painter.value) {
|
||||||
null,
|
null,
|
||||||
is AsyncImagePainter.State.Loading,
|
is AsyncImagePainter.State.Loading,
|
||||||
|
@ -528,24 +526,32 @@ private fun AddedImageFeatures(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is AsyncImagePainter.State.Success -> {
|
is AsyncImagePainter.State.Success -> {
|
||||||
if (content.hash != null) {
|
ShowHash(content, verifiedModifier)
|
||||||
LaunchedEffect(key1 = content.url) {
|
|
||||||
launch(Dispatchers.IO) {
|
|
||||||
val newVerifiedHash = verifyHash(content)
|
|
||||||
if (newVerifiedHash != verifiedHash) {
|
|
||||||
verifiedHash = newVerifiedHash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
verifiedHash?.let { HashVerificationSymbol(it, verifiedModifier) }
|
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ShowHash(
|
||||||
|
content: MediaUrlContent,
|
||||||
|
verifiedModifier: Modifier,
|
||||||
|
) {
|
||||||
|
var verifiedHash by remember(content.url) { mutableStateOf<Boolean?>(null) }
|
||||||
|
|
||||||
|
if (content.hash != null) {
|
||||||
|
LaunchedEffect(key1 = content.url) {
|
||||||
|
val newVerifiedHash = verifyHash(content)
|
||||||
|
if (newVerifiedHash != verifiedHash) {
|
||||||
|
verifiedHash = newVerifiedHash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
verifiedHash?.let { HashVerificationSymbol(it, verifiedModifier) }
|
||||||
|
}
|
||||||
|
|
||||||
fun aspectRatio(dim: String?): Float? {
|
fun aspectRatio(dim: String?): Float? {
|
||||||
if (dim == null) return null
|
if (dim == null) return null
|
||||||
if (dim == "0x0") return null
|
if (dim == "0x0") return null
|
||||||
|
|
|
@ -20,11 +20,11 @@
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
import androidx.compose.animation.animateContentSize
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
@ -83,6 +83,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.chatAuthorBox
|
||||||
import com.vitorpamplona.amethyst.ui.theme.chatAuthorImage
|
import com.vitorpamplona.amethyst.ui.theme.chatAuthorImage
|
||||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
@ -303,7 +304,6 @@ private fun RenderBubble(
|
||||||
bubbleSize.intValue = it.width
|
bubbleSize.intValue = it.width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animateContentSize()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(modifier = bubbleModifier) {
|
Column(modifier = bubbleModifier) {
|
||||||
|
@ -346,6 +346,7 @@ private fun MessageBubbleLines(
|
||||||
baseNote,
|
baseNote,
|
||||||
alignment,
|
alignment,
|
||||||
accountViewModel.settings.showProfilePictures.value,
|
accountViewModel.settings.showProfilePictures.value,
|
||||||
|
accountViewModel,
|
||||||
nav,
|
nav,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -466,13 +467,9 @@ private fun NoteRow(
|
||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
when (note.event) {
|
when (note.event) {
|
||||||
is ChannelCreateEvent -> {
|
is ChannelCreateEvent -> RenderCreateChannelNote(note)
|
||||||
RenderCreateChannelNote(note)
|
is ChannelMetadataEvent -> RenderChangeChannelMetadataNote(note)
|
||||||
}
|
is DraftEvent ->
|
||||||
is ChannelMetadataEvent -> {
|
|
||||||
RenderChangeChannelMetadataNote(note)
|
|
||||||
}
|
|
||||||
is DraftEvent -> {
|
|
||||||
RenderDraftEvent(
|
RenderDraftEvent(
|
||||||
note,
|
note,
|
||||||
canPreview,
|
canPreview,
|
||||||
|
@ -483,8 +480,7 @@ private fun NoteRow(
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
nav,
|
nav,
|
||||||
)
|
)
|
||||||
}
|
else ->
|
||||||
else -> {
|
|
||||||
RenderRegularTextNote(
|
RenderRegularTextNote(
|
||||||
note,
|
note,
|
||||||
canPreview,
|
canPreview,
|
||||||
|
@ -493,7 +489,6 @@ private fun NoteRow(
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
nav,
|
nav,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -710,6 +705,7 @@ private fun DrawAuthorInfo(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
alignment: Arrangement.Horizontal,
|
alignment: Arrangement.Horizontal,
|
||||||
loadProfilePicture: Boolean,
|
loadProfilePicture: Boolean,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
baseNote.author?.let {
|
baseNote.author?.let {
|
||||||
|
@ -723,7 +719,7 @@ private fun DrawAuthorInfo(
|
||||||
nav("User/${baseNote.author?.pubkeyHex}")
|
nav("User/${baseNote.author?.pubkeyHex}")
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
WatchAndDisplayUser(it, loadProfilePicture, nav)
|
WatchAndDisplayUser(it, loadProfilePicture, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -732,11 +728,23 @@ private fun DrawAuthorInfo(
|
||||||
private fun WatchAndDisplayUser(
|
private fun WatchAndDisplayUser(
|
||||||
author: User,
|
author: User,
|
||||||
loadProfilePicture: Boolean,
|
loadProfilePicture: Boolean,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val userState by author.live().userMetadataInfo.observeAsState()
|
val userState by author.live().userMetadataInfo.observeAsState()
|
||||||
|
|
||||||
UserIcon(author.pubkeyHex, userState?.picture, loadProfilePicture)
|
Box(chatAuthorBox, contentAlignment = Alignment.TopEnd) {
|
||||||
|
InnerUserPicture(
|
||||||
|
userHex = author.pubkeyHex,
|
||||||
|
userPicture = userState?.picture,
|
||||||
|
userName = userState?.bestName(),
|
||||||
|
size = Size20dp,
|
||||||
|
modifier = Modifier,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
|
||||||
|
ObserveAndDisplayFollowingMark(author.pubkeyHex, Size5dp, accountViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
if (userState != null) {
|
if (userState != null) {
|
||||||
DisplayMessageUsername(userState?.bestName() ?: author.pubkeyDisplayHex(), userState?.tags ?: EmptyTagList)
|
DisplayMessageUsername(userState?.bestName() ?: author.pubkeyDisplayHex(), userState?.tags ?: EmptyTagList)
|
||||||
|
|
|
@ -25,6 +25,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||||
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.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
@ -70,19 +71,11 @@ fun LoadDecryptedContentOrNull(
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
inner: @Composable (String?) -> Unit,
|
inner: @Composable (String?) -> Unit,
|
||||||
) {
|
) {
|
||||||
var decryptedContent by
|
val decryptedContent by
|
||||||
remember(note.event) {
|
produceState(initialValue = accountViewModel.cachedDecrypt(note), key1 = note.event?.id()) {
|
||||||
mutableStateOf(
|
accountViewModel.decrypt(note) { value = it }
|
||||||
accountViewModel.cachedDecrypt(note),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decryptedContent == null) {
|
|
||||||
LaunchedEffect(key1 = decryptedContent) {
|
|
||||||
accountViewModel.decrypt(note) { decryptedContent = it }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inner(decryptedContent)
|
inner(decryptedContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,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.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
@ -54,6 +53,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.commons.compose.produceCachedStateAsync
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
@ -562,7 +562,7 @@ private fun RenderNoteRow(
|
||||||
is AppDefinitionEvent -> RenderAppDefinition(baseNote, accountViewModel, nav)
|
is AppDefinitionEvent -> RenderAppDefinition(baseNote, accountViewModel, nav)
|
||||||
is AudioTrackEvent -> RenderAudioTrack(baseNote, accountViewModel, nav)
|
is AudioTrackEvent -> RenderAudioTrack(baseNote, accountViewModel, nav)
|
||||||
is AudioHeaderEvent -> RenderAudioHeader(baseNote, accountViewModel, nav)
|
is AudioHeaderEvent -> RenderAudioHeader(baseNote, accountViewModel, nav)
|
||||||
is DraftEvent -> RenderDraft(baseNote, backgroundColor, accountViewModel, nav)
|
is DraftEvent -> RenderDraft(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
is ReactionEvent -> RenderReaction(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
is ReactionEvent -> RenderReaction(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
is RepostEvent -> RenderRepost(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
is RepostEvent -> RenderRepost(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
is GenericRepostEvent -> RenderRepost(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
is GenericRepostEvent -> RenderRepost(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
|
@ -724,16 +724,8 @@ fun ObserveDraftEvent(
|
||||||
val noteState by note.live().metadata.observeAsState()
|
val noteState by note.live().metadata.observeAsState()
|
||||||
|
|
||||||
val noteEvent = noteState?.note?.event as? DraftEvent ?: return
|
val noteEvent = noteState?.note?.event as? DraftEvent ?: return
|
||||||
val noteAuthor = noteState?.note?.author ?: return
|
|
||||||
|
|
||||||
val innerNote =
|
val innerNote = produceCachedStateAsync(cache = accountViewModel.draftNoteCache, key = noteEvent)
|
||||||
produceState(initialValue = accountViewModel.createTempCachedDraftNote(noteEvent, noteAuthor), noteEvent.id) {
|
|
||||||
if (value == null || value?.event?.id() != noteEvent.id) {
|
|
||||||
accountViewModel.createTempDraftNote(noteEvent, noteAuthor) {
|
|
||||||
value = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
innerNote.value?.let {
|
innerNote.value?.let {
|
||||||
render(it)
|
render(it)
|
||||||
|
@ -743,6 +735,7 @@ fun ObserveDraftEvent(
|
||||||
@Composable
|
@Composable
|
||||||
fun RenderDraft(
|
fun RenderDraft(
|
||||||
note: Note,
|
note: Note,
|
||||||
|
quotesLeft: Int,
|
||||||
backgroundColor: MutableState<Color>,
|
backgroundColor: MutableState<Color>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
|
@ -756,7 +749,7 @@ fun RenderDraft(
|
||||||
makeItShort = false,
|
makeItShort = false,
|
||||||
canPreview = true,
|
canPreview = true,
|
||||||
editState = edits,
|
editState = edits,
|
||||||
quotesLeft = 3,
|
quotesLeft = quotesLeft,
|
||||||
unPackReply = true,
|
unPackReply = true,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
|
|
|
@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.screen
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
@ -32,18 +31,13 @@ import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.pullrefresh.PullRefreshIndicator
|
|
||||||
import androidx.compose.material3.pullrefresh.pullRefresh
|
|
||||||
import androidx.compose.material3.pullrefresh.rememberPullRefreshState
|
|
||||||
import androidx.compose.runtime.Composable
|
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.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.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
@ -68,30 +62,8 @@ fun RefreshableCardView(
|
||||||
scrollStateKey: String? = null,
|
scrollStateKey: String? = null,
|
||||||
enablePullRefresh: Boolean = true,
|
enablePullRefresh: Boolean = true,
|
||||||
) {
|
) {
|
||||||
var refreshing by remember { mutableStateOf(false) }
|
RefresheableBox(viewModel, enablePullRefresh) {
|
||||||
val pullRefreshState =
|
|
||||||
rememberPullRefreshState(
|
|
||||||
refreshing,
|
|
||||||
onRefresh = {
|
|
||||||
refreshing = true
|
|
||||||
viewModel.invalidateData()
|
|
||||||
refreshing = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
val modifier =
|
|
||||||
if (enablePullRefresh) {
|
|
||||||
Modifier.fillMaxSize().pullRefresh(pullRefreshState)
|
|
||||||
} else {
|
|
||||||
Modifier.fillMaxSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(modifier) {
|
|
||||||
SaveableCardFeedState(viewModel, accountViewModel, nav, routeForLastRead, scrollStateKey)
|
SaveableCardFeedState(viewModel, accountViewModel, nav, routeForLastRead, scrollStateKey)
|
||||||
|
|
||||||
if (enablePullRefresh) {
|
|
||||||
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ class NotificationViewModel(val account: Account) :
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
|
open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel(), InvalidatableViewModel {
|
||||||
private val _feedContent = MutableStateFlow<CardFeedState>(CardFeedState.Loading)
|
private val _feedContent = MutableStateFlow<CardFeedState>(CardFeedState.Loading)
|
||||||
val feedContent = _feedContent.asStateFlow()
|
val feedContent = _feedContent.asStateFlow()
|
||||||
|
|
||||||
|
@ -358,7 +358,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
|
||||||
private val bundler = BundledUpdate(1000, Dispatchers.IO)
|
private val bundler = BundledUpdate(1000, Dispatchers.IO)
|
||||||
private val bundlerInsert = BundledInsert<Set<Note>>(1000, Dispatchers.IO)
|
private val bundlerInsert = BundledInsert<Set<Note>>(1000, Dispatchers.IO)
|
||||||
|
|
||||||
fun invalidateData(ignoreIfDoing: Boolean = false) {
|
override fun invalidateData(ignoreIfDoing: Boolean) {
|
||||||
bundler.invalidate(ignoreIfDoing) {
|
bundler.invalidate(ignoreIfDoing) {
|
||||||
// adds the time to perform the refresh into this delay
|
// adds the time to perform the refresh into this delay
|
||||||
// holding off new updates in case of heavy refresh routines.
|
// holding off new updates in case of heavy refresh routines.
|
||||||
|
@ -367,9 +367,9 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invalidateDataAndSendToTop() {
|
fun invalidateDataAndSendToTop(ignoreIfDoing: Boolean) {
|
||||||
clear()
|
clear()
|
||||||
bundler.invalidate(false) {
|
bundler.invalidate(ignoreIfDoing) {
|
||||||
// adds the time to perform the refresh into this delay
|
// adds the time to perform the refresh into this delay
|
||||||
// holding off new updates in case of heavy refresh routines.
|
// holding off new updates in case of heavy refresh routines.
|
||||||
val (value, elapsed) =
|
val (value, elapsed) =
|
||||||
|
|
|
@ -21,23 +21,16 @@
|
||||||
package com.vitorpamplona.amethyst.ui.screen
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.pullrefresh.PullRefreshIndicator
|
|
||||||
import androidx.compose.material3.pullrefresh.pullRefresh
|
|
||||||
import androidx.compose.material3.pullrefresh.rememberPullRefreshState
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
@ -57,7 +50,7 @@ import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class RelayFeedViewModel : ViewModel() {
|
class RelayFeedViewModel : ViewModel(), InvalidatableViewModel {
|
||||||
val order =
|
val order =
|
||||||
compareByDescending<RelayInfo> { it.lastEvent }
|
compareByDescending<RelayInfo> { it.lastEvent }
|
||||||
.thenByDescending { it.counter }
|
.thenByDescending { it.counter }
|
||||||
|
@ -112,8 +105,8 @@ class RelayFeedViewModel : ViewModel() {
|
||||||
|
|
||||||
private val bundler = BundledUpdate(250, Dispatchers.IO)
|
private val bundler = BundledUpdate(250, Dispatchers.IO)
|
||||||
|
|
||||||
fun invalidateData() {
|
override fun invalidateData(ignoreIfDoing: Boolean) {
|
||||||
bundler.invalidate {
|
bundler.invalidate(ignoreIfDoing) {
|
||||||
// adds the time to perform the refresh into this delay
|
// adds the time to perform the refresh into this delay
|
||||||
// holding off new updates in case of heavy refresh routines.
|
// holding off new updates in case of heavy refresh routines.
|
||||||
refreshSuspended()
|
refreshSuspended()
|
||||||
|
@ -142,22 +135,7 @@ fun RelayFeedView(
|
||||||
NewRelayListView({ wantsToAddRelay = "" }, accountViewModel, wantsToAddRelay, nav = nav)
|
NewRelayListView({ wantsToAddRelay = "" }, accountViewModel, wantsToAddRelay, nav = nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
var refreshing by remember { mutableStateOf(false) }
|
RefresheableBox(viewModel, enablePullRefresh) {
|
||||||
val refresh = {
|
|
||||||
refreshing = true
|
|
||||||
viewModel.refresh()
|
|
||||||
refreshing = false
|
|
||||||
}
|
|
||||||
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
|
||||||
|
|
||||||
val modifier =
|
|
||||||
if (enablePullRefresh) {
|
|
||||||
Modifier.fillMaxSize().pullRefresh(pullRefreshState)
|
|
||||||
} else {
|
|
||||||
Modifier.fillMaxSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(modifier) {
|
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
@ -176,9 +154,5 @@ fun RelayFeedView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enablePullRefresh) {
|
|
||||||
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -554,7 +554,7 @@ fun NoteMaster(
|
||||||
} else if (noteEvent is AppDefinitionEvent) {
|
} else if (noteEvent is AppDefinitionEvent) {
|
||||||
RenderAppDefinition(baseNote, accountViewModel, nav)
|
RenderAppDefinition(baseNote, accountViewModel, nav)
|
||||||
} else if (noteEvent is DraftEvent) {
|
} else if (noteEvent is DraftEvent) {
|
||||||
RenderDraft(baseNote, backgroundColor, accountViewModel, nav)
|
RenderDraft(baseNote, 3, backgroundColor, accountViewModel, nav)
|
||||||
} else if (noteEvent is HighlightEvent) {
|
} else if (noteEvent is HighlightEvent) {
|
||||||
DisplayHighlight(
|
DisplayHighlight(
|
||||||
noteEvent.quote(),
|
noteEvent.quote(),
|
||||||
|
|
|
@ -38,6 +38,7 @@ import coil.request.ImageRequest
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ServiceManager
|
import com.vitorpamplona.amethyst.ServiceManager
|
||||||
import com.vitorpamplona.amethyst.commons.compose.GenericBaseCache
|
import com.vitorpamplona.amethyst.commons.compose.GenericBaseCache
|
||||||
|
import com.vitorpamplona.amethyst.commons.compose.GenericBaseCacheAsync
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.AccountState
|
import com.vitorpamplona.amethyst.model.AccountState
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
|
@ -1322,23 +1323,11 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
|
||||||
account.deleteDraft(draftTag)
|
account.deleteDraft(draftTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createTempCachedDraftNote(
|
suspend fun createTempDraftNote(
|
||||||
noteEvent: DraftEvent,
|
noteEvent: DraftEvent,
|
||||||
author: User,
|
onReady: (Note?) -> Unit,
|
||||||
): Note? {
|
|
||||||
return noteEvent.preCachedDraft(account.signer)?.let { createTempDraftNote(it, author) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createTempDraftNote(
|
|
||||||
noteEvent: DraftEvent,
|
|
||||||
author: User,
|
|
||||||
onReady: (Note) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
draftNoteCache.update(noteEvent, onReady)
|
||||||
noteEvent.cachedDraft(account.signer) {
|
|
||||||
onReady(createTempDraftNote(it, author))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createTempDraftNote(
|
fun createTempDraftNote(
|
||||||
|
@ -1355,6 +1344,21 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
|
||||||
return note
|
return note
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val draftNoteCache = CachedDraftNotes(this)
|
||||||
|
|
||||||
|
class CachedDraftNotes(val accountViewModel: AccountViewModel) : GenericBaseCacheAsync<DraftEvent, Note>(20) {
|
||||||
|
override suspend fun compute(
|
||||||
|
key: DraftEvent,
|
||||||
|
onReady: (Note?) -> Unit,
|
||||||
|
) = withContext(Dispatchers.IO) {
|
||||||
|
key.cachedDraft(accountViewModel.account.signer) {
|
||||||
|
val author = LocalCache.getOrCreateUser(key.pubKey)
|
||||||
|
val note = accountViewModel.createTempDraftNote(it, author)
|
||||||
|
onReady(note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val bechLinkCache = CachedLoadedBechLink(this)
|
val bechLinkCache = CachedLoadedBechLink(this)
|
||||||
|
|
||||||
class CachedLoadedBechLink(val accountViewModel: AccountViewModel) : GenericBaseCache<String, LoadedBechLink>(20) {
|
class CachedLoadedBechLink(val accountViewModel: AccountViewModel) : GenericBaseCache<String, LoadedBechLink>(20) {
|
||||||
|
|
|
@ -146,6 +146,7 @@ import com.vitorpamplona.amethyst.ui.theme.EditFieldLeadingIconModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldModifier
|
import com.vitorpamplona.amethyst.ui.theme.EditFieldModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier
|
import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier
|
import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||||
|
@ -174,7 +175,6 @@ import java.text.DateFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChannelScreen(
|
fun ChannelScreen(
|
||||||
|
@ -328,7 +328,6 @@ fun ChannelScreen(
|
||||||
newPostModel.message = TextFieldValue("")
|
newPostModel.message = TextFieldValue("")
|
||||||
replyTo.value = null
|
replyTo.value = null
|
||||||
accountViewModel.deleteDraft(newPostModel.draftTag)
|
accountViewModel.deleteDraft(newPostModel.draftTag)
|
||||||
newPostModel.draftTag = UUID.randomUUID().toString()
|
|
||||||
feedViewModel.sendToTop()
|
feedViewModel.sendToTop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -976,21 +975,21 @@ private fun ShortChannelActionOptions(
|
||||||
) {
|
) {
|
||||||
LoadNote(baseNoteHex = channel.idHex, accountViewModel) {
|
LoadNote(baseNoteHex = channel.idHex, accountViewModel) {
|
||||||
it?.let {
|
it?.let {
|
||||||
Spacer(modifier = StdHorzSpacer)
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = RowColSpacing) {
|
||||||
LikeReaction(
|
LikeReaction(
|
||||||
baseNote = it,
|
baseNote = it,
|
||||||
grayTint = MaterialTheme.colorScheme.onSurface,
|
grayTint = MaterialTheme.colorScheme.onSurface,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav,
|
nav,
|
||||||
)
|
)
|
||||||
Spacer(modifier = StdHorzSpacer)
|
ZapReaction(
|
||||||
ZapReaction(
|
baseNote = it,
|
||||||
baseNote = it,
|
grayTint = MaterialTheme.colorScheme.onSurface,
|
||||||
grayTint = MaterialTheme.colorScheme.onSurface,
|
accountViewModel = accountViewModel,
|
||||||
accountViewModel = accountViewModel,
|
nav = nav,
|
||||||
nav = nav,
|
)
|
||||||
)
|
Spacer(modifier = StdHorzSpacer)
|
||||||
Spacer(modifier = StdHorzSpacer)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,6 @@ import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatroomScreen(
|
fun ChatroomScreen(
|
||||||
|
@ -356,7 +355,6 @@ fun ChatroomScreen(
|
||||||
accountViewModel.deleteDraft(newPostModel.draftTag)
|
accountViewModel.deleteDraft(newPostModel.draftTag)
|
||||||
|
|
||||||
newPostModel.message = TextFieldValue("")
|
newPostModel.message = TextFieldValue("")
|
||||||
newPostModel.draftTag = UUID.randomUUID().toString()
|
|
||||||
|
|
||||||
replyTo.value = null
|
replyTo.value = null
|
||||||
feedViewModel.sendToTop()
|
feedViewModel.sendToTop()
|
||||||
|
|
|
@ -276,7 +276,7 @@ fun MainScreen(
|
||||||
discoveryChatFeedViewModel.sendToTop()
|
discoveryChatFeedViewModel.sendToTop()
|
||||||
}
|
}
|
||||||
Route.Notification.base -> {
|
Route.Notification.base -> {
|
||||||
notifFeedViewModel.invalidateDataAndSendToTop()
|
notifFeedViewModel.invalidateDataAndSendToTop(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -228,5 +228,6 @@ val liveStreamTag =
|
||||||
.background(Color.Black)
|
.background(Color.Black)
|
||||||
.padding(horizontal = Size5dp)
|
.padding(horizontal = Size5dp)
|
||||||
|
|
||||||
|
val chatAuthorBox = Modifier.size(20.dp)
|
||||||
val chatAuthorImage = Modifier.size(20.dp).clip(shape = CircleShape)
|
val chatAuthorImage = Modifier.size(20.dp).clip(shape = CircleShape)
|
||||||
val AuthorInfoVideoFeed = Modifier.width(75.dp).padding(end = 15.dp)
|
val AuthorInfoVideoFeed = Modifier.width(75.dp).padding(end = 15.dp)
|
||||||
|
|
|
@ -274,6 +274,7 @@
|
||||||
<string name="report_dialog_title">Blokovat a nahlásit</string>
|
<string name="report_dialog_title">Blokovat a nahlásit</string>
|
||||||
<string name="block_only">Blokovat</string>
|
<string name="block_only">Blokovat</string>
|
||||||
<string name="bookmarks">Záložky</string>
|
<string name="bookmarks">Záložky</string>
|
||||||
|
<string name="drafts">Koncepty</string>
|
||||||
<string name="private_bookmarks">Soukromé záložky</string>
|
<string name="private_bookmarks">Soukromé záložky</string>
|
||||||
<string name="public_bookmarks">Veřejné záložky</string>
|
<string name="public_bookmarks">Veřejné záložky</string>
|
||||||
<string name="add_to_private_bookmarks">Přidat do soukromých záložek</string>
|
<string name="add_to_private_bookmarks">Přidat do soukromých záložek</string>
|
||||||
|
@ -620,6 +621,7 @@
|
||||||
<string name="server_did_not_provide_a_url_after_uploading">Server po nahrání neposkytl URL</string>
|
<string name="server_did_not_provide_a_url_after_uploading">Server po nahrání neposkytl URL</string>
|
||||||
<string name="could_not_download_from_the_server">Nepodařilo se stáhnout nahraná média ze serveru</string>
|
<string name="could_not_download_from_the_server">Nepodařilo se stáhnout nahraná média ze serveru</string>
|
||||||
<string name="could_not_prepare_local_file_to_upload">Nelze připravit místní soubor k nahrání: %1$s</string>
|
<string name="could_not_prepare_local_file_to_upload">Nelze připravit místní soubor k nahrání: %1$s</string>
|
||||||
|
<string name="edit_draft">Upravit koncept</string>
|
||||||
<string name="login_with_qr_code">Přihlášení pomocí QR kódu</string>
|
<string name="login_with_qr_code">Přihlášení pomocí QR kódu</string>
|
||||||
<string name="route">Trasa</string>
|
<string name="route">Trasa</string>
|
||||||
<string name="route_home">Domů</string>
|
<string name="route_home">Domů</string>
|
||||||
|
@ -691,4 +693,10 @@
|
||||||
<string name="accessibility_play_username">Přehrát uživatelské jméno jako audio</string>
|
<string name="accessibility_play_username">Přehrát uživatelské jméno jako audio</string>
|
||||||
<string name="accessibility_scan_qr_code">Skenovat QR kód</string>
|
<string name="accessibility_scan_qr_code">Skenovat QR kód</string>
|
||||||
<string name="accessibility_navigate_to_alby">Přejít na poskytovatele peněženky třetí strany Alby</string>
|
<string name="accessibility_navigate_to_alby">Přejít na poskytovatele peněženky třetí strany Alby</string>
|
||||||
|
<string name="it_s_not_possible_to_reply_to_a_draft_note">Není možné odpovědět na koncept</string>
|
||||||
|
<string name="it_s_not_possible_to_quote_to_a_draft_note">Není možné citovat koncept</string>
|
||||||
|
<string name="it_s_not_possible_to_react_to_a_draft_note">Není možné reagovat na koncept</string>
|
||||||
|
<string name="it_s_not_possible_to_zap_to_a_draft_note">Není možné poslat zap konceptu</string>
|
||||||
|
<string name="draft_note">Koncept</string>
|
||||||
|
<string name="load_from_text">Ze zprávy</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -280,6 +280,7 @@ anz der Bedingungen ist erforderlich</string>
|
||||||
<string name="report_dialog_title">Blockieren und melden</string>
|
<string name="report_dialog_title">Blockieren und melden</string>
|
||||||
<string name="block_only">Blockieren</string>
|
<string name="block_only">Blockieren</string>
|
||||||
<string name="bookmarks">Lesezeichen</string>
|
<string name="bookmarks">Lesezeichen</string>
|
||||||
|
<string name="drafts">Entwürfe</string>
|
||||||
<string name="private_bookmarks">Private Lesezeichen</string>
|
<string name="private_bookmarks">Private Lesezeichen</string>
|
||||||
<string name="public_bookmarks">Öffentliche Lesezeichen</string>
|
<string name="public_bookmarks">Öffentliche Lesezeichen</string>
|
||||||
<string name="add_to_private_bookmarks">Zu den privaten Lesezeichen hinzufügen</string>
|
<string name="add_to_private_bookmarks">Zu den privaten Lesezeichen hinzufügen</string>
|
||||||
|
@ -625,6 +626,7 @@ anz der Bedingungen ist erforderlich</string>
|
||||||
<string name="server_did_not_provide_a_url_after_uploading">Der Server hat nach dem Hochladen keine URL angegeben</string>
|
<string name="server_did_not_provide_a_url_after_uploading">Der Server hat nach dem Hochladen keine URL angegeben</string>
|
||||||
<string name="could_not_download_from_the_server">Hochgeladene Medien konnten nicht vom Server heruntergeladen werden</string>
|
<string name="could_not_download_from_the_server">Hochgeladene Medien konnten nicht vom Server heruntergeladen werden</string>
|
||||||
<string name="could_not_prepare_local_file_to_upload">Lokale Datei konnte nicht zum Hochladen vorbereitet werden: %1$s</string>
|
<string name="could_not_prepare_local_file_to_upload">Lokale Datei konnte nicht zum Hochladen vorbereitet werden: %1$s</string>
|
||||||
|
<string name="edit_draft">Entwurf bearbeiten</string>
|
||||||
<string name="login_with_qr_code">Einloggen mit QR-Code</string>
|
<string name="login_with_qr_code">Einloggen mit QR-Code</string>
|
||||||
<string name="route">Route</string>
|
<string name="route">Route</string>
|
||||||
<string name="route_home">Startseite</string>
|
<string name="route_home">Startseite</string>
|
||||||
|
@ -696,4 +698,10 @@ anz der Bedingungen ist erforderlich</string>
|
||||||
<string name="accessibility_play_username">Benutzernamen als Audio abspielen</string>
|
<string name="accessibility_play_username">Benutzernamen als Audio abspielen</string>
|
||||||
<string name="accessibility_scan_qr_code">QR-Code scannen</string>
|
<string name="accessibility_scan_qr_code">QR-Code scannen</string>
|
||||||
<string name="accessibility_navigate_to_alby">Navigieren Sie zum Drittanbieter-Wallet-Anbieter Alby</string>
|
<string name="accessibility_navigate_to_alby">Navigieren Sie zum Drittanbieter-Wallet-Anbieter Alby</string>
|
||||||
|
<string name="it_s_not_possible_to_reply_to_a_draft_note">Es ist nicht möglich, auf einen Entwurf zu antworten</string>
|
||||||
|
<string name="it_s_not_possible_to_quote_to_a_draft_note">Es ist nicht möglich, einen Entwurf zu zitieren</string>
|
||||||
|
<string name="it_s_not_possible_to_react_to_a_draft_note">Es ist nicht möglich, auf einen Entwurf zu reagieren</string>
|
||||||
|
<string name="it_s_not_possible_to_zap_to_a_draft_note">Es ist nicht möglich, zap Zahlung an einen Entwurf senden</string>
|
||||||
|
<string name="draft_note">Entwurf</string>
|
||||||
|
<string name="load_from_text">Aus Nachricht laden</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -276,6 +276,7 @@
|
||||||
<string name="report_dialog_title">Bloquear y reportar</string>
|
<string name="report_dialog_title">Bloquear y reportar</string>
|
||||||
<string name="block_only">Bloquear</string>
|
<string name="block_only">Bloquear</string>
|
||||||
<string name="bookmarks">Marcadores</string>
|
<string name="bookmarks">Marcadores</string>
|
||||||
|
<string name="drafts">Borradores</string>
|
||||||
<string name="private_bookmarks">Marcadores privados</string>
|
<string name="private_bookmarks">Marcadores privados</string>
|
||||||
<string name="public_bookmarks">Marcadores públicos</string>
|
<string name="public_bookmarks">Marcadores públicos</string>
|
||||||
<string name="add_to_private_bookmarks">Agregar a marcadores privados</string>
|
<string name="add_to_private_bookmarks">Agregar a marcadores privados</string>
|
||||||
|
|
|
@ -276,6 +276,7 @@
|
||||||
<string name="report_dialog_title">Bloquear y reportar</string>
|
<string name="report_dialog_title">Bloquear y reportar</string>
|
||||||
<string name="block_only">Bloquear</string>
|
<string name="block_only">Bloquear</string>
|
||||||
<string name="bookmarks">Marcadores</string>
|
<string name="bookmarks">Marcadores</string>
|
||||||
|
<string name="drafts">Borradores</string>
|
||||||
<string name="private_bookmarks">Marcadores privados</string>
|
<string name="private_bookmarks">Marcadores privados</string>
|
||||||
<string name="public_bookmarks">Marcadores públicos</string>
|
<string name="public_bookmarks">Marcadores públicos</string>
|
||||||
<string name="add_to_private_bookmarks">Agregar a marcadores privados</string>
|
<string name="add_to_private_bookmarks">Agregar a marcadores privados</string>
|
||||||
|
|
|
@ -276,6 +276,7 @@
|
||||||
<string name="report_dialog_title">Bloquer et Signaler</string>
|
<string name="report_dialog_title">Bloquer et Signaler</string>
|
||||||
<string name="block_only">Bloquer</string>
|
<string name="block_only">Bloquer</string>
|
||||||
<string name="bookmarks">Favoris</string>
|
<string name="bookmarks">Favoris</string>
|
||||||
|
<string name="drafts">Brouillons</string>
|
||||||
<string name="private_bookmarks">Favoris Privés</string>
|
<string name="private_bookmarks">Favoris Privés</string>
|
||||||
<string name="public_bookmarks">Favoris Publics</string>
|
<string name="public_bookmarks">Favoris Publics</string>
|
||||||
<string name="add_to_private_bookmarks">Ajouter aux Favoris Privés</string>
|
<string name="add_to_private_bookmarks">Ajouter aux Favoris Privés</string>
|
||||||
|
|
|
@ -276,6 +276,7 @@
|
||||||
<string name="report_dialog_title">Blokkeer en rapporteer</string>
|
<string name="report_dialog_title">Blokkeer en rapporteer</string>
|
||||||
<string name="block_only">Blokkeren</string>
|
<string name="block_only">Blokkeren</string>
|
||||||
<string name="bookmarks">Bladwijzers</string>
|
<string name="bookmarks">Bladwijzers</string>
|
||||||
|
<string name="drafts">Concepten</string>
|
||||||
<string name="private_bookmarks">Privé bladwijzers</string>
|
<string name="private_bookmarks">Privé bladwijzers</string>
|
||||||
<string name="public_bookmarks">Publieke Bladwijzers</string>
|
<string name="public_bookmarks">Publieke Bladwijzers</string>
|
||||||
<string name="add_to_private_bookmarks">Toevoegen aan privé bladwijzers</string>
|
<string name="add_to_private_bookmarks">Toevoegen aan privé bladwijzers</string>
|
||||||
|
@ -441,6 +442,8 @@
|
||||||
<string name="connectivity_type_always">Altijd</string>
|
<string name="connectivity_type_always">Altijd</string>
|
||||||
<string name="connectivity_type_wifi_only">Alleen wifi</string>
|
<string name="connectivity_type_wifi_only">Alleen wifi</string>
|
||||||
<string name="connectivity_type_never">Nooit</string>
|
<string name="connectivity_type_never">Nooit</string>
|
||||||
|
<string name="ui_feature_set_type_complete">Voltooid</string>
|
||||||
|
<string name="ui_feature_set_type_simplified">Vereenvoudigd</string>
|
||||||
<string name="system">Systeem</string>
|
<string name="system">Systeem</string>
|
||||||
<string name="light">Licht</string>
|
<string name="light">Licht</string>
|
||||||
<string name="dark">Donker</string>
|
<string name="dark">Donker</string>
|
||||||
|
@ -452,6 +455,8 @@
|
||||||
<string name="automatically_show_url_preview">Automatisch URL preview tonen</string>
|
<string name="automatically_show_url_preview">Automatisch URL preview tonen</string>
|
||||||
<string name="automatically_hide_nav_bars">Volledig scrollen</string>
|
<string name="automatically_hide_nav_bars">Volledig scrollen</string>
|
||||||
<string name="automatically_hide_nav_bars_description">Navigatie verbergen bij scrollen</string>
|
<string name="automatically_hide_nav_bars_description">Navigatie verbergen bij scrollen</string>
|
||||||
|
<string name="ui_style">UI modus</string>
|
||||||
|
<string name="ui_style_description">Kies de stijl van het bericht</string>
|
||||||
<string name="load_image">Afbeelding laden</string>
|
<string name="load_image">Afbeelding laden</string>
|
||||||
<string name="spamming_users">Spammers</string>
|
<string name="spamming_users">Spammers</string>
|
||||||
<string name="muted_button">Geen geluid. Klik om voor geluid</string>
|
<string name="muted_button">Geen geluid. Klik om voor geluid</string>
|
||||||
|
@ -618,6 +623,7 @@
|
||||||
<string name="server_did_not_provide_a_url_after_uploading">Server heeft geen URL opgegeven na uploaden</string>
|
<string name="server_did_not_provide_a_url_after_uploading">Server heeft geen URL opgegeven na uploaden</string>
|
||||||
<string name="could_not_download_from_the_server">Kan de geüploade media niet downloaden van de server</string>
|
<string name="could_not_download_from_the_server">Kan de geüploade media niet downloaden van de server</string>
|
||||||
<string name="could_not_prepare_local_file_to_upload">Kon lokaal bestand niet voorbereiden voor upload: %1$s</string>
|
<string name="could_not_prepare_local_file_to_upload">Kon lokaal bestand niet voorbereiden voor upload: %1$s</string>
|
||||||
|
<string name="edit_draft">Concept bewerken</string>
|
||||||
<string name="login_with_qr_code">Inloggen met QR-Code</string>
|
<string name="login_with_qr_code">Inloggen met QR-Code</string>
|
||||||
<string name="route">Route</string>
|
<string name="route">Route</string>
|
||||||
<string name="route_home">Beginscherm</string>
|
<string name="route_home">Beginscherm</string>
|
||||||
|
@ -689,4 +695,9 @@
|
||||||
<string name="accessibility_play_username">Speel gebruikersnaam af als audio</string>
|
<string name="accessibility_play_username">Speel gebruikersnaam af als audio</string>
|
||||||
<string name="accessibility_scan_qr_code">Scan de QR-code</string>
|
<string name="accessibility_scan_qr_code">Scan de QR-code</string>
|
||||||
<string name="accessibility_navigate_to_alby">Navigeer naar de externe wallet provider Alby</string>
|
<string name="accessibility_navigate_to_alby">Navigeer naar de externe wallet provider Alby</string>
|
||||||
|
<string name="it_s_not_possible_to_reply_to_a_draft_note">Het is niet mogelijk om een concept te beantwoorden</string>
|
||||||
|
<string name="it_s_not_possible_to_quote_to_a_draft_note">Het is niet mogelijk om een concept te citeren</string>
|
||||||
|
<string name="it_s_not_possible_to_react_to_a_draft_note">Het is niet mogelijk om op een concept te reageren</string>
|
||||||
|
<string name="it_s_not_possible_to_zap_to_a_draft_note">Het is niet mogelijk om een concept op te zappen</string>
|
||||||
|
<string name="draft_note">Concept notitie</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<string name="point_to_the_qr_code">Aponte para o código QR</string>
|
<string name="point_to_the_qr_code">Aponte para o QR code</string>
|
||||||
<string name="show_qr">Mostrar QR</string>
|
<string name="show_qr">Mostrar QR</string>
|
||||||
<string name="profile_image">Imagem de perfil</string>
|
<string name="profile_image">Imagem de perfil</string>
|
||||||
<string name="your_profile_image">Sua Foto de Perfil</string>
|
<string name="your_profile_image">Sua Foto de Perfil</string>
|
||||||
|
@ -274,6 +274,7 @@
|
||||||
<string name="report_dialog_title">Bloquear e Denunciar</string>
|
<string name="report_dialog_title">Bloquear e Denunciar</string>
|
||||||
<string name="block_only">Bloquear</string>
|
<string name="block_only">Bloquear</string>
|
||||||
<string name="bookmarks">Itens Salvos</string>
|
<string name="bookmarks">Itens Salvos</string>
|
||||||
|
<string name="drafts">Rascunhos</string>
|
||||||
<string name="private_bookmarks">Itens Salvos Privados</string>
|
<string name="private_bookmarks">Itens Salvos Privados</string>
|
||||||
<string name="public_bookmarks">Itens Salvos Públicos</string>
|
<string name="public_bookmarks">Itens Salvos Públicos</string>
|
||||||
<string name="add_to_private_bookmarks">Adicionar aos Itens Salvos Privados</string>
|
<string name="add_to_private_bookmarks">Adicionar aos Itens Salvos Privados</string>
|
||||||
|
@ -620,6 +621,7 @@
|
||||||
<string name="server_did_not_provide_a_url_after_uploading">O servidor não forneceu uma URL após o upload</string>
|
<string name="server_did_not_provide_a_url_after_uploading">O servidor não forneceu uma URL após o upload</string>
|
||||||
<string name="could_not_download_from_the_server">Não foi possível baixar o arquivo de mídia carregado do servidor</string>
|
<string name="could_not_download_from_the_server">Não foi possível baixar o arquivo de mídia carregado do servidor</string>
|
||||||
<string name="could_not_prepare_local_file_to_upload">Não foi possível preparar o arquivo local para enviar: %1$s</string>
|
<string name="could_not_prepare_local_file_to_upload">Não foi possível preparar o arquivo local para enviar: %1$s</string>
|
||||||
|
<string name="edit_draft">Editar rascunho</string>
|
||||||
<string name="login_with_qr_code">Entrar com Código QR</string>
|
<string name="login_with_qr_code">Entrar com Código QR</string>
|
||||||
<string name="route">Rota</string>
|
<string name="route">Rota</string>
|
||||||
<string name="route_home">Início</string>
|
<string name="route_home">Início</string>
|
||||||
|
@ -691,4 +693,10 @@
|
||||||
<string name="accessibility_play_username">Reproduzir nome de usuário como áudio</string>
|
<string name="accessibility_play_username">Reproduzir nome de usuário como áudio</string>
|
||||||
<string name="accessibility_scan_qr_code">Escanear código QR</string>
|
<string name="accessibility_scan_qr_code">Escanear código QR</string>
|
||||||
<string name="accessibility_navigate_to_alby">Navegar para o provedor de carteira de terceiros Alby</string>
|
<string name="accessibility_navigate_to_alby">Navegar para o provedor de carteira de terceiros Alby</string>
|
||||||
|
<string name="it_s_not_possible_to_reply_to_a_draft_note">Não é possível responder uma nota em rascunho</string>
|
||||||
|
<string name="it_s_not_possible_to_quote_to_a_draft_note">Não é possível citar uma nota em rascunho</string>
|
||||||
|
<string name="it_s_not_possible_to_react_to_a_draft_note">Não é possível reagir uma nota em rascunho</string>
|
||||||
|
<string name="it_s_not_possible_to_zap_to_a_draft_note">Não é possível fazer um zap em uma nota em rascunho</string>
|
||||||
|
<string name="draft_note">Nota de rascunho</string>
|
||||||
|
<string name="load_from_text">Carregar do texto</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -274,6 +274,7 @@
|
||||||
<string name="report_dialog_title">Blockera och rapportera</string>
|
<string name="report_dialog_title">Blockera och rapportera</string>
|
||||||
<string name="block_only">Blockera</string>
|
<string name="block_only">Blockera</string>
|
||||||
<string name="bookmarks">Bokmärken</string>
|
<string name="bookmarks">Bokmärken</string>
|
||||||
|
<string name="drafts">Utkast</string>
|
||||||
<string name="private_bookmarks">Privata Bokmärken</string>
|
<string name="private_bookmarks">Privata Bokmärken</string>
|
||||||
<string name="public_bookmarks">Publika Bokmärken</string>
|
<string name="public_bookmarks">Publika Bokmärken</string>
|
||||||
<string name="add_to_private_bookmarks">Lägg till i Privata Bokmärken</string>
|
<string name="add_to_private_bookmarks">Lägg till i Privata Bokmärken</string>
|
||||||
|
@ -619,6 +620,7 @@
|
||||||
<string name="server_did_not_provide_a_url_after_uploading">Servern gav inte en URL efter uppladdning</string>
|
<string name="server_did_not_provide_a_url_after_uploading">Servern gav inte en URL efter uppladdning</string>
|
||||||
<string name="could_not_download_from_the_server">Kunde inte ladda ner uppladdade medier från servern</string>
|
<string name="could_not_download_from_the_server">Kunde inte ladda ner uppladdade medier från servern</string>
|
||||||
<string name="could_not_prepare_local_file_to_upload">Kunde inte förbereda lokal fil att ladda upp: %1$s</string>
|
<string name="could_not_prepare_local_file_to_upload">Kunde inte förbereda lokal fil att ladda upp: %1$s</string>
|
||||||
|
<string name="edit_draft">Redigera utkast</string>
|
||||||
<string name="login_with_qr_code">Logga in med QR-kod</string>
|
<string name="login_with_qr_code">Logga in med QR-kod</string>
|
||||||
<string name="route">Rutt</string>
|
<string name="route">Rutt</string>
|
||||||
<string name="route_home">Hem</string>
|
<string name="route_home">Hem</string>
|
||||||
|
@ -690,4 +692,10 @@
|
||||||
<string name="accessibility_play_username">Spela upp användarnamn som ljud</string>
|
<string name="accessibility_play_username">Spela upp användarnamn som ljud</string>
|
||||||
<string name="accessibility_scan_qr_code">Skanna QR-kod</string>
|
<string name="accessibility_scan_qr_code">Skanna QR-kod</string>
|
||||||
<string name="accessibility_navigate_to_alby">Navigera till tredjeparts plånboksleverantören Alby</string>
|
<string name="accessibility_navigate_to_alby">Navigera till tredjeparts plånboksleverantören Alby</string>
|
||||||
|
<string name="it_s_not_possible_to_reply_to_a_draft_note">Det går inte att svara på ett utkast</string>
|
||||||
|
<string name="it_s_not_possible_to_quote_to_a_draft_note">Det går inte att citera ett utkast</string>
|
||||||
|
<string name="it_s_not_possible_to_react_to_a_draft_note">Det går inte att reagera på ett utkast</string>
|
||||||
|
<string name="it_s_not_possible_to_zap_to_a_draft_note">Det går inte att zappa ett utkast</string>
|
||||||
|
<string name="draft_note">Utkast</string>
|
||||||
|
<string name="load_from_text">Från meddelande</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -827,4 +827,6 @@
|
||||||
<string name="it_s_not_possible_to_react_to_a_draft_note">It\'s not possible to react a draft note</string>
|
<string name="it_s_not_possible_to_react_to_a_draft_note">It\'s not possible to react a draft note</string>
|
||||||
<string name="it_s_not_possible_to_zap_to_a_draft_note">It\'s not possible to zap a draft note</string>
|
<string name="it_s_not_possible_to_zap_to_a_draft_note">It\'s not possible to zap a draft note</string>
|
||||||
<string name="draft_note">Draft Note</string>
|
<string name="draft_note">Draft Note</string>
|
||||||
|
|
||||||
|
<string name="load_from_text">From Msg</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.commons.compose
|
||||||
|
|
||||||
|
import android.util.LruCache
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun <K, V> produceCachedStateAsync(
|
||||||
|
cache: AsyncCachedState<K, V>,
|
||||||
|
key: K,
|
||||||
|
): State<V?> {
|
||||||
|
return produceState(initialValue = cache.cached(key), key1 = key) {
|
||||||
|
cache.update(key) {
|
||||||
|
value = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun <K, V> produceCachedStateAsync(
|
||||||
|
cache: AsyncCachedState<K, V>,
|
||||||
|
key: String,
|
||||||
|
updateValue: K,
|
||||||
|
): State<V?> {
|
||||||
|
return produceState(initialValue = cache.cached(updateValue), key1 = key) {
|
||||||
|
cache.update(updateValue) {
|
||||||
|
value = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AsyncCachedState<K, V> {
|
||||||
|
fun cached(k: K): V?
|
||||||
|
|
||||||
|
suspend fun update(
|
||||||
|
k: K,
|
||||||
|
onReady: (V?) -> Unit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class GenericBaseCacheAsync<K, V>(capacity: Int) : AsyncCachedState<K, V> {
|
||||||
|
private val cache = LruCache<K, V>(capacity)
|
||||||
|
|
||||||
|
override fun cached(k: K): V? {
|
||||||
|
return cache[k]
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(
|
||||||
|
k: K,
|
||||||
|
onReady: (V?) -> Unit,
|
||||||
|
) {
|
||||||
|
cache[k]?.let { onReady(it) }
|
||||||
|
|
||||||
|
compute(k) {
|
||||||
|
if (it != null) {
|
||||||
|
cache.put(k, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
onReady(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract suspend fun compute(
|
||||||
|
key: K,
|
||||||
|
onReady: (V?) -> Unit,
|
||||||
|
)
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentSkipListMap
|
||||||
import java.util.function.BiConsumer
|
import java.util.function.BiConsumer
|
||||||
|
|
||||||
class LargeCache<K, V> {
|
class LargeCache<K, V> {
|
||||||
val cache = ConcurrentSkipListMap<K, V>()
|
private val cache = ConcurrentSkipListMap<K, V>()
|
||||||
|
|
||||||
fun get(key: K) = cache.get(key)
|
fun get(key: K) = cache.get(key)
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,13 @@ class DraftEvent(
|
||||||
onReady: (Event) -> Unit,
|
onReady: (Event) -> Unit,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
plainContent(signer) { onReady(fromJson(it)) }
|
plainContent(signer) {
|
||||||
|
try {
|
||||||
|
onReady(fromJson(it))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Log.e("UnwrapError", "Couldn't Decrypt the content", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Log.e("UnwrapError", "Couldn't Decrypt the content", e)
|
// Log.e("UnwrapError", "Couldn't Decrypt the content", e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,13 @@ class GiftWrapEvent(
|
||||||
return cachedInnerEvent[signer.pubKey]
|
return cachedInnerEvent[signer.pubKey]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addToCache(
|
||||||
|
pubKey: HexKey,
|
||||||
|
gift: Event,
|
||||||
|
) {
|
||||||
|
cachedInnerEvent = cachedInnerEvent + Pair(pubKey, gift)
|
||||||
|
}
|
||||||
|
|
||||||
fun cachedGift(
|
fun cachedGift(
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
onReady: (Event) -> Unit,
|
onReady: (Event) -> Unit,
|
||||||
|
@ -56,7 +63,7 @@ class GiftWrapEvent(
|
||||||
if (gift is WrappedEvent) {
|
if (gift is WrappedEvent) {
|
||||||
gift.host = this
|
gift.host = this
|
||||||
}
|
}
|
||||||
cachedInnerEvent = cachedInnerEvent + Pair(signer.pubKey, gift)
|
addToCache(signer.pubKey, gift)
|
||||||
|
|
||||||
onReady(gift)
|
onReady(gift)
|
||||||
}
|
}
|
||||||
|
@ -98,8 +105,8 @@ class GiftWrapEvent(
|
||||||
val serializedContent = toJson(event)
|
val serializedContent = toJson(event)
|
||||||
val tags = arrayOf(arrayOf("p", recipientPubKey))
|
val tags = arrayOf(arrayOf("p", recipientPubKey))
|
||||||
|
|
||||||
signer.nip44Encrypt(serializedContent, recipientPubKey) {
|
signer.nip44Encrypt(serializedContent, recipientPubKey) { content ->
|
||||||
signer.sign(createdAt, KIND, tags, it, onReady)
|
signer.sign(createdAt, KIND, tags, content, onReady)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,13 @@ class SealedGossipEvent(
|
||||||
return cachedInnerEvent[signer.pubKey]
|
return cachedInnerEvent[signer.pubKey]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addToCache(
|
||||||
|
pubKey: HexKey,
|
||||||
|
gift: Event,
|
||||||
|
) {
|
||||||
|
cachedInnerEvent = cachedInnerEvent + Pair(pubKey, gift)
|
||||||
|
}
|
||||||
|
|
||||||
fun cachedGossip(
|
fun cachedGossip(
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
onReady: (Event) -> Unit,
|
onReady: (Event) -> Unit,
|
||||||
|
@ -59,8 +66,8 @@ class SealedGossipEvent(
|
||||||
if (event is WrappedEvent) {
|
if (event is WrappedEvent) {
|
||||||
event.host = host ?: this
|
event.host = host ?: this
|
||||||
}
|
}
|
||||||
|
addToCache(signer.pubKey, event)
|
||||||
|
|
||||||
cachedInnerEvent = cachedInnerEvent + Pair(signer.pubKey, event)
|
|
||||||
onReady(event)
|
onReady(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue