Merge remote-tracking branch 'origin/main' into claude/cashu-wallet-wizard-0zr280

This commit is contained in:
Claude
2026-06-26 18:56:50 +00:00
10 changed files with 133 additions and 52 deletions
@@ -2045,6 +2045,14 @@ class Account(
extraNotesToBroadcast.forEach { client.publish(it, relays) }
}
/**
* The live [AddressableNote] backing a draft tag for this account. It is the same cached
* note that draft events are consumed into, so its `event` tracks the draft over time. The
* composer holds onto it (via DraftTagState) so [LocalCache]'s weak reference can't collect
* it before a deletion needs it, which would otherwise orphan the draft on the relays.
*/
fun getOrCreateDraftNote(draftTag: String): AddressableNote = cache.getOrCreateAddressableNote(DraftWrapEvent.createAddress(signer.pubKey, draftTag))
suspend fun createAndSendDraftIgnoreErrors(
draftTag: String,
template: EventTemplate<out Event>,
@@ -2081,18 +2089,25 @@ class Account(
}
}
suspend fun deleteDraftIgnoreErrors(draftTag: String) {
suspend fun deleteDraftIgnoreErrors(draftNote: AddressableNote?) {
try {
deleteDraftInner(draftTag)
deleteDraftInner(draftNote)
} catch (e: Exception) {
if (e is CancellationException) throw e
}
}
suspend fun deleteDraftInner(draftTag: String) {
suspend fun deleteDraftInner(draftNote: AddressableNote?) {
if (!isWriteable()) return
val extraRelays = cache.getAddressableNoteIfExists(DraftWrapEvent.createAddressTag(signer.pubKey, draftTag))?.relays ?: emptyList()
// Only a real, still-present draft needs a deletion signed. The note's event is null when
// no draft was ever saved (e.g. auto-drafts disabled) and already empty once it has been
// deleted — in both cases there is nothing to delete, so we avoid prompting the signer.
val draftEvent = draftNote?.event as? DraftWrapEvent
if (draftEvent == null || draftEvent.isDeleted()) return
val draftTag = draftNote.dTag()
val extraRelays = draftNote.relays
val deletedDraft = DraftWrapEvent.createDeletedEvent(draftTag, signer)
val deletionEvent = signer.sign(DeletionEvent.build(listOf(deletedDraft)))
@@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor
import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord
import com.vitorpamplona.amethyst.commons.ui.text.setTextAndPlaceCursorAtBeginning
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
@@ -136,11 +137,17 @@ open class CommentPostViewModel :
IExpiration {
val draftTag = DraftTagState()
// Strong reference to the live cache note for the current draft tag (derived from the
// versions flow below), so LocalCache cannot weakly collect it before a deletion needs it.
var draftNote: AddressableNote? = null
private set
init {
viewModelScope.launch(Dispatchers.IO) {
draftTag.versions.collectLatest {
// don't save the first
if (it > 0) {
draftNote = account.getOrCreateDraftNote(draftTag.current)
sendDraftSync()
}
}
@@ -261,6 +268,7 @@ open class CommentPostViewModel :
val oldTag = (draft.event as? AddressableEvent)?.dTag()
if (oldTag != null) {
draftTag.set(oldTag)
draftNote = account.getOrCreateDraftNote(oldTag)
}
loadFromDraft(innerNote)
}
@@ -489,7 +497,7 @@ open class CommentPostViewModel :
}
}
val version = draftTag.current
val draftToDelete = draftNote
val anonymous = wantsAnonymousPost
cancel()
@@ -500,14 +508,14 @@ open class CommentPostViewModel :
}
accountViewModel.viewModelScope.launch(Dispatchers.IO) {
accountViewModel.account.deleteDraftIgnoreErrors(version)
accountViewModel.account.deleteDraftIgnoreErrors(draftToDelete)
}
}
suspend fun sendDraftSync() {
if (message.text.toString().isBlank()) {
accountViewModel.account.deleteDraftIgnoreErrors(draftTag.current)
} else {
accountViewModel.account.deleteDraftIgnoreErrors(draftNote)
} else if (accountViewModel.settings.automaticallyCreateDrafts()) {
val attachments = mutableSetOf<Event>()
nip95attachments.forEach {
attachments.add(it.first)
@@ -37,6 +37,7 @@ import com.vitorpamplona.amethyst.commons.ui.text.currentWord
import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor
import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
@@ -126,6 +127,11 @@ class ChatNewMessageViewModel :
IExpiration {
val draftTag = DraftTagState()
// Strong reference to the live cache note for the current draft tag (derived from the
// versions flow below), so LocalCache cannot weakly collect it before a deletion needs it.
var draftNote: AddressableNote? = null
private set
lateinit var accountViewModel: AccountViewModel
lateinit var account: Account
@@ -134,6 +140,7 @@ class ChatNewMessageViewModel :
draftTag.versions.collectLatest {
// don't save the first
if (it > 0) {
draftNote = account.getOrCreateDraftNote(draftTag.current)
accountViewModel.launchSigner {
sendDraftSync()
}
@@ -327,6 +334,7 @@ class ChatNewMessageViewModel :
val oldTag = (draft.event as? AddressableEvent)?.dTag()
if (oldTag != null) {
draftTag.set(oldTag)
draftNote = account.getOrCreateDraftNote(oldTag)
}
loadFromDraft(innerNote)
}
@@ -418,17 +426,17 @@ class ChatNewMessageViewModel :
}
suspend fun sendPostSync() {
val version = draftTag.current
val draftToDelete = draftNote
innerSendPost(null)
cancel()
accountViewModel.viewModelScope.launch(Dispatchers.IO) {
accountViewModel.account.deleteDraftIgnoreErrors(version)
accountViewModel.account.deleteDraftIgnoreErrors(draftToDelete)
}
}
suspend fun sendDraftSync() {
if (message.text.toString().isBlank()) {
account.deleteDraftIgnoreErrors(draftTag.current)
account.deleteDraftIgnoreErrors(draftNote)
} else if (accountViewModel.settings.automaticallyCreateDrafts()) {
innerSendPost(draftTag.current)
}
@@ -43,6 +43,7 @@ import com.vitorpamplona.amethyst.commons.ui.text.currentWord
import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor
import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
@@ -112,11 +113,17 @@ open class ChannelNewMessageViewModel :
IExpiration {
val draftTag = DraftTagState()
// Strong reference to the live cache note for the current draft tag (derived from the
// versions flow below), so LocalCache cannot weakly collect it before a deletion needs it.
var draftNote: AddressableNote? = null
private set
init {
viewModelScope.launch(Dispatchers.IO) {
draftTag.versions.collectLatest {
// don't save the first
if (it > 0) {
draftNote = account.getOrCreateDraftNote(draftTag.current)
accountViewModel.launchSigner {
sendDraftSync()
}
@@ -229,6 +236,7 @@ open class ChannelNewMessageViewModel :
val oldTag = (draft.event as? AddressableEvent)?.dTag()
if (oldTag != null) {
draftTag.set(oldTag)
draftNote = account.getOrCreateDraftNote(oldTag)
}
loadFromDraft(innerNote)
}
@@ -301,20 +309,20 @@ open class ChannelNewMessageViewModel :
val template = createTemplate() ?: return
val channelRelays = channel?.relays() ?: emptySet()
val version = draftTag.current
val draftToDelete = draftNote
cancel()
accountViewModel.account.signAndSendPrivatelyOrBroadcast(template) {
channelRelays.toList()
}
accountViewModel.viewModelScope.launch(Dispatchers.IO) {
accountViewModel.account.deleteDraftIgnoreErrors(version)
accountViewModel.account.deleteDraftIgnoreErrors(draftToDelete)
}
}
suspend fun sendDraftSync() {
if (message.text.toString().isBlank()) {
account.deleteDraftIgnoreErrors(draftTag.current)
account.deleteDraftIgnoreErrors(draftNote)
} else if (accountViewModel.settings.automaticallyCreateDrafts()) {
val attachments = mutableSetOf<Event>()
nip95attachments.forEach {
@@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.commons.ui.text.currentWord
import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor
import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
@@ -128,6 +129,11 @@ class LongFormPostViewModel :
IExpiration {
val draftTag = DraftTagState()
// Strong reference to the live cache note for the current draft tag (derived from the
// versions flow below), so LocalCache cannot weakly collect it before a deletion needs it.
var draftNote: AddressableNote? = null
private set
lateinit var accountViewModel: AccountViewModel
lateinit var account: Account
@@ -136,6 +142,7 @@ class LongFormPostViewModel :
draftTag.versions.collectLatest {
// don't save the first
if (it > 0) {
draftNote = account.getOrCreateDraftNote(draftTag.current)
accountViewModel.launchSigner {
sendDraftSync()
}
@@ -237,6 +244,7 @@ class LongFormPostViewModel :
val oldTag = (draft.event as? AddressableEvent)?.dTag()
if (oldTag != null) {
draftTag.set(oldTag)
draftNote = account.getOrCreateDraftNote(oldTag)
}
loadFromDraft(innerNote)
}
@@ -324,7 +332,7 @@ class LongFormPostViewModel :
suspend fun sendPostSync() {
val template = createTemplate() ?: return
val version = draftTag.current
val draftToDelete = draftNote
cancel()
if (accountViewModel.settings.useTrackedBroadcasts()) {
@@ -342,13 +350,13 @@ class LongFormPostViewModel :
}
accountViewModel.launchSigner {
accountViewModel.account.deleteDraftIgnoreErrors(version)
accountViewModel.account.deleteDraftIgnoreErrors(draftToDelete)
}
}
suspend fun sendDraftSync() {
if (message.text.toString().isBlank() && title.text.isBlank()) {
accountViewModel.account.deleteDraftIgnoreErrors(draftTag.current)
accountViewModel.account.deleteDraftIgnoreErrors(draftNote)
} else if (accountViewModel.settings.automaticallyCreateDrafts()) {
val template = createTemplate() ?: return
accountViewModel.account.createAndSendDraftIgnoreErrors(draftTag.current, template, emptySet())
@@ -38,6 +38,7 @@ import com.vitorpamplona.amethyst.commons.ui.text.currentWord
import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor
import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
@@ -114,6 +115,11 @@ open class NewProductViewModel :
IExpiration {
val draftTag = DraftTagState()
// Strong reference to the live cache note for the current draft tag (derived from the
// versions flow below), so LocalCache cannot weakly collect it before a deletion needs it.
var draftNote: AddressableNote? = null
private set
lateinit var accountViewModel: AccountViewModel
lateinit var account: Account
@@ -122,6 +128,7 @@ open class NewProductViewModel :
draftTag.versions.collectLatest {
// don't save the first
if (it > 0) {
draftNote = account.getOrCreateDraftNote(draftTag.current)
accountViewModel.launchSigner {
sendDraftSync()
}
@@ -218,6 +225,7 @@ open class NewProductViewModel :
val oldTag = (draft.event as? AddressableEvent)?.dTag()
if (oldTag != null) {
draftTag.set(oldTag)
draftNote = account.getOrCreateDraftNote(oldTag)
}
loadFromDraft(innerNote)
}
@@ -315,18 +323,18 @@ open class NewProductViewModel :
suspend fun sendPostSync() {
val template = createTemplate() ?: return
val version = draftTag.current
val draftToDelete = draftNote
cancel()
accountViewModel.account.signAndSendPrivatelyOrBroadcast(template, relayList = { relayList })
accountViewModel.viewModelScope.launch(Dispatchers.IO) {
accountViewModel.account.deleteDraftIgnoreErrors(version)
accountViewModel.account.deleteDraftIgnoreErrors(draftToDelete)
}
}
suspend fun sendDraftSync() {
if (message.text.toString().isBlank()) {
accountViewModel.account.deleteDraftIgnoreErrors(draftTag.current)
accountViewModel.account.deleteDraftIgnoreErrors(draftNote)
} else if (accountViewModel.settings.automaticallyCreateDrafts()) {
val template = createTemplate() ?: return
accountViewModel.account.createAndSendDraftIgnoreErrors(draftTag.current, template)
@@ -41,6 +41,7 @@ import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor
import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord
import com.vitorpamplona.amethyst.commons.ui.text.setTextAndPlaceCursorAtBeginning
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.BooleanType
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
@@ -183,6 +184,11 @@ open class ShortNotePostViewModel :
IExpiration {
val draftTag = DraftTagState()
// Strong reference to the live cache note for the current draft tag (derived from the
// versions flow below), so LocalCache cannot weakly collect it before a deletion needs it.
var draftNote: AddressableNote? = null
private set
lateinit var accountViewModel: AccountViewModel
lateinit var account: Account
@@ -191,6 +197,7 @@ open class ShortNotePostViewModel :
draftTag.versions.collectLatest {
// don't save the first
if (it > 0) {
draftNote = account.getOrCreateDraftNote(draftTag.current)
accountViewModel.launchSigner {
sendDraftSync()
}
@@ -483,6 +490,7 @@ open class ShortNotePostViewModel :
val oldTag = (draft.event as? AddressableEvent)?.dTag()
if (oldTag != null) {
draftTag.set(oldTag)
draftNote = account.getOrCreateDraftNote(oldTag)
}
loadFromDraft(innerNote)
}
@@ -889,7 +897,7 @@ open class ShortNotePostViewModel :
}
}
val version = draftTag.current
val draftToDelete = draftNote
val anonymous = wantsAnonymousPost
val scheduledFor = scheduledForSec
val privately = wantsPrivateNote
@@ -903,7 +911,7 @@ open class ShortNotePostViewModel :
@Suppress("UNCHECKED_CAST")
accountViewModel.account.sendPrivateNote(template as EventTemplate<TextNoteEvent>)
accountViewModel.launchSigner {
accountViewModel.account.deleteDraftIgnoreErrors(version)
accountViewModel.account.deleteDraftIgnoreErrors(draftToDelete)
}
return
}
@@ -935,7 +943,7 @@ open class ShortNotePostViewModel :
),
)
accountViewModel.launchSigner {
accountViewModel.account.deleteDraftIgnoreErrors(version)
accountViewModel.account.deleteDraftIgnoreErrors(draftToDelete)
}
return
}
@@ -961,13 +969,13 @@ open class ShortNotePostViewModel :
}
accountViewModel.launchSigner {
accountViewModel.account.deleteDraftIgnoreErrors(version)
accountViewModel.account.deleteDraftIgnoreErrors(draftToDelete)
}
}
suspend fun sendDraftSync() {
if (message.text.toString().isBlank()) {
accountViewModel.account.deleteDraftIgnoreErrors(draftTag.current)
accountViewModel.account.deleteDraftIgnoreErrors(draftNote)
} else if (accountViewModel.settings.automaticallyCreateDrafts()) {
val attachments = mutableSetOf<Event>()
nip95attachments.forEach {
@@ -118,11 +118,17 @@ open class NestNewMessageViewModel :
IExpiration {
val draftTag = DraftTagState()
// Strong reference to the live cache note for the current draft tag (derived from the
// versions flow below), so LocalCache cannot weakly collect it before a deletion needs it.
var draftNote: AddressableNote? = null
private set
init {
viewModelScope.launch(Dispatchers.IO) {
draftTag.versions.collectLatest {
// don't save the first
if (it > 0) {
draftNote = account.getOrCreateDraftNote(draftTag.current)
accountViewModel.launchSigner {
sendDraftSync()
}
@@ -235,6 +241,7 @@ open class NestNewMessageViewModel :
val oldTag = (draft.event as? AddressableEvent)?.dTag()
if (oldTag != null) {
draftTag.set(oldTag)
draftNote = account.getOrCreateDraftNote(oldTag)
}
loadFromDraft(innerNote)
}
@@ -303,7 +310,7 @@ open class NestNewMessageViewModel :
suspend fun sendPostSync() {
val template = createTemplate() ?: return
val version = draftTag.current
val draftToDelete = draftNote
cancel()
// Broadcast to the user's default relays — the nest has no
@@ -312,13 +319,13 @@ open class NestNewMessageViewModel :
// `account.signAndComputeBroadcast(...)`.
accountViewModel.account.signAndComputeBroadcast(template)
accountViewModel.viewModelScope.launch(Dispatchers.IO) {
accountViewModel.account.deleteDraftIgnoreErrors(version)
accountViewModel.account.deleteDraftIgnoreErrors(draftToDelete)
}
}
suspend fun sendDraftSync() {
if (message.text.toString().isBlank()) {
account.deleteDraftIgnoreErrors(draftTag.current)
account.deleteDraftIgnoreErrors(draftNote)
} else if (accountViewModel.settings.automaticallyCreateDrafts()) {
val attachments = mutableSetOf<Event>()
nip95attachments.forEach {
@@ -38,6 +38,7 @@ import com.vitorpamplona.amethyst.commons.ui.text.currentWord
import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor
import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
@@ -129,6 +130,11 @@ class NewPublicMessageViewModel :
IExpiration {
val draftTag = DraftTagState()
// Strong reference to the live cache note for the current draft tag (derived from the
// versions flow below), so LocalCache cannot weakly collect it before a deletion needs it.
var draftNote: AddressableNote? = null
private set
lateinit var accountViewModel: AccountViewModel
lateinit var account: Account
@@ -137,6 +143,7 @@ class NewPublicMessageViewModel :
draftTag.versions.collectLatest {
// don't save the first
if (it > 0) {
draftNote = account.getOrCreateDraftNote(draftTag.current)
accountViewModel.launchSigner {
sendDraftSync()
}
@@ -262,6 +269,7 @@ class NewPublicMessageViewModel :
val oldTag = (draft.event as? AddressableEvent)?.dTag()
if (oldTag != null) {
draftTag.set(oldTag)
draftNote = account.getOrCreateDraftNote(oldTag)
}
loadFromDraft(innerNote)
}
@@ -340,18 +348,18 @@ class NewPublicMessageViewModel :
}
}
val version = draftTag.current
val draftToDelete = draftNote
cancel()
accountViewModel.account.signAndComputeBroadcast(template, extraNotesToBroadcast)
accountViewModel.viewModelScope.launch(Dispatchers.IO) {
accountViewModel.account.deleteDraftIgnoreErrors(version)
accountViewModel.account.deleteDraftIgnoreErrors(draftToDelete)
}
}
suspend fun sendDraftSync() {
if (message.text.toString().isBlank()) {
accountViewModel.account.deleteDraftIgnoreErrors(draftTag.current)
accountViewModel.account.deleteDraftIgnoreErrors(draftNote)
} else if (accountViewModel.settings.automaticallyCreateDrafts()) {
val broadcast = mutableSetOf<Event>()
nip95attachments.forEach {
@@ -3,31 +3,34 @@
~
~ Notification small icons are rendered by the system as flat, alpha-only
~ silhouettes, so the regular `amethyst` gem becomes an identical solid gem
~ for both real notifications and this persistent service. To stop the
~ service from reading as a new notification, this variant draws the gem as
~ a hollow outline — same brand shape, clearly different at a glance.
~ for both real notifications and this persistent service. To set the
~ always-on service apart, this variant centres the brand gem inside a ring
~ of three "server" nodes — signalling a background service quietly connected
~ to a network of relays, clearly different from a one-off notification at a
~ glance. The gem is grown to the largest size that still clears the nodes.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<!--
The gem outline below is authored at ~half the viewport size, leaving it
visibly smaller than every other notification small icon (the filled
`amethyst` gem fills the viewport). Scale it up ~1.8x about its centre so it
matches the others, keeping a margin so the stroke doesn't clip at the edges.
-->
<group
android:pivotX="256"
android:pivotY="261"
android:scaleX="1.8"
android:scaleY="1.8">
<path
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeWidth="22"
android:strokeLineJoin="round"
android:pathData="M370.18,326.86C369.91,326.04 307.3,176.97 290.09,135.63c-20.96,0 -65.23,0 -96.46,0 -9.17,22.67 -32.82,80.44 -51.8,126.98 10.4,24.81 19.74,47.1 26.95,64.29l39.28,0a2.68,2.68 135,0 0,2.51 -3.15c-0.34,-7.93 -21.93,-36.76 -15.49,-67.94 1.72,-6.71 3.03,-13.53 4.91,-20.21a235.88,235.88 135,0 1,18.04 -46.7c1.19,-2.27 1.74,-3.29 4.5,-1.57 5.86,3.65 12.41,3.44 18.94,2.97 5.62,-1.22 8.36,-3.92 18.67,-1.18 2.04,0 4.03,0 6.11,0.19 4.95,0.39 9.6,1.57 12.57,6.05 3.25,4.9 3.77,10.44 3.25,16.1 -0.79,8.47 -0.23,16.36 6.94,22.33a77.19,77.19 0,0 0,8.05 5.37c3.56,2.28 7.85,3.27 10.9,6.44 1.8,1.87 2.86,3.85 2.08,6.51 -0.78,2.66 -2.86,3.91 -5.6,4.2 -6.92,0.75 -13.69,-0.66 -20.49,-1.48 -0.89,-0.11 -1.76,-0.17 -2.68,-0.23a15.39,15.39 0,0 1,-5.11 0.17l-1.6,0a35.83,35.83 0,0 0,-7.92 1.89c-6.41,2.34 -12.25,4.91 -19.82,4.78a5.8,5.8 0,0 0,-2.75 1.19c-5.93,5.27 -9.5,11.93 -6.54,21.66 8.13,22.72 29,77.53 37.96,101.18 -0.25,-16.02 -0.32,-44.71 -0.38,-58.64 35.8,0.05 90.7,0.03 95.08,0.03z" />
</group>
<path
android:fillColor="#000000"
android:fillType="evenOdd"
android:pathData="M384.871,330.838C384.567,329.917,313.907,161.667,294.473,115c-23.662,0,-73.63,0,-108.877,0c-10.343,25.583,-37.047,90.79,-58.468,143.318c11.74,28.006,22.282,53.163,30.412,72.57l44.332,0a3.03,3.03 0,0,0,2.831,-3.56c-0.381,-8.953,-24.749,-41.49,-17.48,-76.687c1.937,-7.573,3.422,-15.267,5.542,-22.812a266.236,266.236 135,0,1,20.356,-52.711c1.347,-2.561,1.97,-3.709,5.072,-1.772c6.618,4.118,14.008,3.88,21.371,3.345c6.347,-1.38,9.438,-4.421,21.068,-1.33c2.302,0,4.543,0,6.894,0.21c5.591,0.442,10.829,1.772,14.191,6.833c3.665,5.53,4.255,11.784,3.665,18.176c-0.894,9.56,-0.259,18.463,7.832,25.202a87.125,87.125 0,0,0,9.091,6.06c4.013,2.578,8.859,3.698,12.297,7.269c2.031,2.103,3.229,4.349,2.346,7.346c-0.878,2.997,-3.229,4.41,-6.314,4.741c-7.816,0.85,-15.449,-0.74,-23.127,-1.667c-0.999,-0.121,-1.981,-0.199,-3.03,-0.259a17.375,17.375 0,0,1,-5.773,0.199l-1.805,0a40.441,40.441 0,0,0,-8.936,2.136c-7.242,2.638,-13.832,5.542,-22.37,5.393a6.546,6.546 0,0,0,-3.107,1.347c-6.695,5.95,-10.724,13.467,-7.374,24.446c9.179,25.643,32.73,87.505,42.853,114.209c-0.287,-18.087,-0.364,-50.47,-0.425,-66.189c40.413,0.061,102.375,0.028,107.315,0.028ZM248.64,201.534a8.544,8.693 90,1,0,17.386,0a8.544,8.693 90,1,0,-17.386,0Z" />
<path
android:strokeColor="#000000"
android:strokeWidth="20"
android:fillColor="#00000000"
android:strokeLineJoin="round"
android:pathData="M256,46A210,210 0,1,1 256,466A210,210 0,1,1 256,46Z" />
<path
android:fillColor="#000000"
android:pathData="M256,10A36,36 0,1,1 256,82A36,36 0,1,1 256,10Z" />
<path
android:fillColor="#000000"
android:pathData="M74.13,325A36,36 0,1,1 74.13,397A36,36 0,1,1 74.13,325Z" />
<path
android:fillColor="#000000"
android:pathData="M437.87,325A36,36 0,1,1 437.87,397A36,36 0,1,1 437.87,325Z" />
</vector>