Full Wallet Connect onboarding with Alby.

This commit is contained in:
Vitor Pamplona
2023-03-30 16:10:13 -04:00
parent b4e409db4d
commit b4e39f2b73
7 changed files with 134 additions and 41 deletions

View File

@@ -43,6 +43,13 @@
<data android:scheme="nostr" /> <data android:scheme="nostr" />
</intent-filter> </intent-filter>
<intent-filter android:label="Amethyst">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nostrwalletconnect" />
</intent-filter>
<meta-data <meta-data
android:name="android.app.lib_name" android:name="android.app.lib_name"
android:value="" /> android:value="" />

View File

@@ -21,6 +21,8 @@ import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.ServiceManager import com.vitorpamplona.amethyst.ServiceManager
import com.vitorpamplona.amethyst.service.nip19.Nip19 import com.vitorpamplona.amethyst.service.nip19.Nip19
import com.vitorpamplona.amethyst.service.relays.Client import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.navigation.Route
import com.vitorpamplona.amethyst.ui.note.Nip47
import com.vitorpamplona.amethyst.ui.screen.AccountScreen import com.vitorpamplona.amethyst.ui.screen.AccountScreen
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
@@ -28,6 +30,8 @@ import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
class MainActivity : FragmentActivity() { class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -40,6 +44,14 @@ class MainActivity : FragmentActivity() {
Nip19.Type.EVENT -> "Event/${nip19.hex}" Nip19.Type.EVENT -> "Event/${nip19.hex}"
Nip19.Type.ADDRESS -> "Note/${nip19.hex}" Nip19.Type.ADDRESS -> "Note/${nip19.hex}"
else -> null else -> null
} ?: try {
intent?.data?.toString()?.let {
Nip47.parse(it)
val encodedUri = URLEncoder.encode(it, StandardCharsets.UTF_8.toString())
Route.Home.base + "?nip47=" + encodedUri
}
} catch (e: Exception) {
null
} }
Coil.setImageLoader { Coil.setImageLoader {

View File

@@ -1,8 +1,12 @@
package com.vitorpamplona.amethyst.ui.navigation package com.vitorpamplona.amethyst.ui.navigation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
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.remember
import androidx.compose.runtime.setValue
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
@@ -39,6 +43,7 @@ fun AppNavigation(
nextPage: String? = null nextPage: String? = null
) { ) {
val homePagerState = rememberPagerState() val homePagerState = rememberPagerState()
var actionableNextPage by remember { mutableStateOf<String?>(nextPage) }
// Avoids creating ViewModels for performance reasons (up to 1 second delays) // Avoids creating ViewModels for performance reasons (up to 1 second delays)
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
@@ -76,8 +81,9 @@ fun AppNavigation(
} }
Route.Home.let { route -> Route.Home.let { route ->
composable(route.route, route.arguments, content = { composable(route.route, route.arguments, content = { it ->
val scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false val scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false
val nip47 = it.arguments?.getString("nip47")
HomeScreen( HomeScreen(
homeFeedViewModel = homeFeedViewModel, homeFeedViewModel = homeFeedViewModel,
@@ -85,13 +91,17 @@ fun AppNavigation(
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, navController = navController,
pagerState = homePagerState, pagerState = homePagerState,
scrollToTop = scrollToTop scrollToTop = scrollToTop,
nip47 = nip47
) )
// Avoids running scroll to top when back button is pressed // Avoids running scroll to top when back button is pressed
if (scrollToTop) { if (scrollToTop) {
it.arguments?.remove("scrollToTop") it.arguments?.remove("scrollToTop")
} }
if (nip47 != null) {
it.arguments?.remove("nip47")
}
}) })
} }
@@ -178,7 +188,10 @@ fun AppNavigation(
} }
} }
if (nextPage != null) { actionableNextPage?.let {
navController.navigate(nextPage) LaunchedEffect(it) {
navController.navigate(it)
}
actionableNextPage = null
} }
} }

View File

@@ -24,9 +24,12 @@ sealed class Route(
get() = route.substringBefore("?") get() = route.substringBefore("?")
object Home : Route( object Home : Route(
route = "Home?scrollToTop={scrollToTop}", route = "Home?scrollToTop={scrollToTop}&nip47={nip47}",
icon = R.drawable.ic_home, icon = R.drawable.ic_home,
arguments = listOf(navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false }), arguments = listOf(
navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false },
navArgument("nip47") { type = NavType.StringType; nullable = true; defaultValue = null }
),
hasNewItems = { accountViewModel, cache -> homeHasNewItems(accountViewModel, cache) } hasNewItems = { accountViewModel, cache -> homeHasNewItems(accountViewModel, cache) }
) )

View File

@@ -49,6 +49,7 @@ 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.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
@@ -165,17 +166,40 @@ class UpdateZapAmountViewModel : ViewModel() {
walletConnectSecret.text != (account?.zapPaymentRequest?.secret ?: "") walletConnectSecret.text != (account?.zapPaymentRequest?.secret ?: "")
) )
} }
fun updateNIP47(uri: String) {
val contact = Nip47.parse(uri)
if (contact != null) {
walletConnectPubkey =
TextFieldValue(contact.pubKeyHex)
walletConnectRelay =
TextFieldValue(contact.relayUri ?: "")
walletConnectSecret =
TextFieldValue(contact.secret ?: "")
}
}
} }
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account) { fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account, nip47uri: String? = null) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val postViewModel: UpdateZapAmountViewModel = viewModel() val postViewModel: UpdateZapAmountViewModel = viewModel()
val uri = LocalUriHandler.current
LaunchedEffect(account) { LaunchedEffect(account) {
postViewModel.load(account) postViewModel.load(account)
if (nip47uri != null) {
try {
postViewModel.updateNIP47(nip47uri)
} catch (e: IllegalArgumentException) {
scope.launch {
Toast.makeText(context, e.message, Toast.LENGTH_SHORT)
.show()
}
}
}
} }
Dialog( Dialog(
@@ -299,6 +323,18 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account) {
stringResource(id = R.string.wallet_connect_service), stringResource(id = R.string.wallet_connect_service),
Modifier.weight(1f) Modifier.weight(1f)
) )
IconButton(onClick = {
runCatching { uri.openUri("https://nwc.getalby.com/apps/new?c=Amethyst") }
}) {
Icon(
painter = painterResource(R.drawable.alby),
null,
modifier = Modifier.size(24.dp),
tint = Color.Unspecified
)
}
IconButton(onClick = { IconButton(onClick = {
qrScanning = true qrScanning = true
}) { }) {
@@ -330,15 +366,7 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account) {
qrScanning = false qrScanning = false
if (!it.isNullOrEmpty()) { if (!it.isNullOrEmpty()) {
try { try {
val contact = Nip47.parse(it) postViewModel.updateNIP47(it)
if (contact != null) {
postViewModel.walletConnectPubkey =
TextFieldValue(contact.pubKeyHex)
postViewModel.walletConnectRelay =
TextFieldValue(contact.relayUri ?: "")
postViewModel.walletConnectSecret =
TextFieldValue(contact.secret ?: "")
}
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
scope.launch { scope.launch {
Toast.makeText(context, e.message, Toast.LENGTH_SHORT) Toast.makeText(context, e.message, Toast.LENGTH_SHORT)

View File

@@ -11,7 +11,11 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -28,6 +32,7 @@ import com.vitorpamplona.amethyst.service.NostrHomeDataSource
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.navigation.Route
import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountDialog
import com.vitorpamplona.amethyst.ui.screen.FeedView import com.vitorpamplona.amethyst.ui.screen.FeedView
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
@@ -42,10 +47,12 @@ fun HomeScreen(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, navController: NavController,
pagerState: PagerState, pagerState: PagerState,
scrollToTop: Boolean = false scrollToTop: Boolean = false,
nip47: String? = null
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val account = accountViewModel.accountLiveData.value?.account ?: return val account = accountViewModel.accountLiveData.value?.account ?: return
var wantsToAddNip47 by remember { mutableStateOf<String?>(nip47) }
LaunchedEffect(accountViewModel) { LaunchedEffect(accountViewModel) {
HomeNewThreadFeedFilter.account = account HomeNewThreadFeedFilter.account = account
@@ -55,6 +62,10 @@ fun HomeScreen(
repliesFeedViewModel.invalidateData() repliesFeedViewModel.invalidateData()
} }
if (wantsToAddNip47 != null) {
UpdateZapAmountDialog({ wantsToAddNip47 = null }, account = account, wantsToAddNip47)
}
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(accountViewModel) { DisposableEffect(accountViewModel) {
val observer = LifecycleEventObserver { _, event -> val observer = LifecycleEventObserver { _, event ->

View File

@@ -1,39 +1,58 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="250dp" android:width="489dp"
android:height="250dp" android:height="489dp"
android:viewportWidth="250" android:viewportWidth="489"
android:viewportHeight="250"> android:viewportHeight="489">
<path <path
android:pathData="M48.12,70.63m18.75,0a18.75,18.75 0,1 0,-37.5 0a18.75,18.75 0,1 0,37.5 0" android:pathData="M89.58,181.77C96.83,182.51 103.84,181.66 110.26,179.49L110.42,179.63L110.66,179.87C83.35,210.92 64.09,251.1 56.14,295.56C50.5,327.03 67.39,357.29 95.35,370.61C140.92,392.29 191.9,404.42 245.64,404.42C299.39,404.42 349.32,392.54 394.53,371.27C422.59,358.07 439.6,327.82 434.06,296.31C426.2,251.6 406.52,211.48 379.48,179.96C345.33,140.15 379.58,179.8 379.58,179.8C379.58,179.8 391.32,182.34 397.62,181.9C421.93,180.22 441.52,160.37 442.89,136.03C444.51,106.88 420.52,82.9 391.36,84.54C367.36,85.89 347.63,105.01 345.58,128.97C344.95,136.44 346.01,143.62 348.41,150.15L348.02,150.54L347.79,150.77C318.32,129.03 283.17,116.22 245.05,116.22C206.93,116.22 171.54,129.12 141.99,151.01L141.77,150.78L141.4,150.42L140.69,149.71C143.01,143.22 144.01,136.11 143.33,128.71C141.16,104.88 121.47,85.9 97.57,84.55C68.36,82.92 44.37,106.91 46.01,136.09C47.34,159.78 65.97,179.34 89.56,181.78L89.58,181.77Z"
android:fillColor="#000000"/> android:strokeWidth="19.962"
android:fillColor="#ffffff"
android:strokeColor="#ffffff"/>
<path <path
android:pathData="M45,67.82L80,102.82" android:pathData="M104.48,351.42C84.72,342.01 73.22,320.89 77.07,299.33C85.11,254.32 105.57,214.8 133.95,186.08C138.28,181.69 142.81,177.57 147.49,173.7C175.19,150.87 208.78,137.51 244.99,137.51C281.2,137.51 314.55,150.78 342.19,173.45C346.89,177.3 351.41,181.43 355.76,185.81C384.43,214.69 405.06,254.58 413.04,300.02C416.84,321.6 405.24,342.72 385.41,352.05C342.98,372.01 295.59,383.17 245.6,383.17C195.6,383.17 147.22,371.79 104.45,351.42H104.48Z"
android:strokeWidth="9.37484"
android:fillColor="#00000000"
android:strokeColor="#000000"/>
<path
android:pathData="M201.25,70.63m-18.75,0a18.75,18.75 0,1 1,37.5 0a18.75,18.75 0,1 1,-37.5 0"
android:fillColor="#000000"/>
<path
android:pathData="M204.68,67.82L169.68,102.82"
android:strokeWidth="9.37484"
android:fillColor="#00000000"
android:strokeColor="#000000"/>
<path
android:pathData="M53.45,182.51C43.34,177.7 37.46,166.9 39.43,155.88C47.89,108.59 83.12,73.13 125.31,73.13C167.6,73.13 202.91,108.77 211.25,156.23C213.19,167.26 207.26,178.06 197.12,182.83C175.43,193.04 151.19,198.75 125.62,198.75C99.79,198.75 75.32,192.92 53.45,182.51Z"
android:fillColor="#FFDF6F" android:fillColor="#FFDF6F"
android:fillType="evenOdd"/> android:fillType="evenOdd"/>
<path <path
android:pathData="M211.25,156.23L206.64,157.04L211.25,156.23ZM53.45,182.51L51.43,186.74L53.45,182.51ZM44.04,156.71C52.21,111.01 85.99,77.82 125.31,77.82V68.44C80.26,68.44 43.56,106.17 34.82,155.06L44.04,156.71ZM125.31,77.82C164.73,77.82 198.57,111.18 206.64,157.04L215.87,155.41C207.24,106.36 170.47,68.44 125.31,68.44V77.82ZM195.13,178.59C174.04,188.51 150.49,194.06 125.62,194.06V203.44C151.9,203.44 176.81,197.57 199.12,187.07L195.13,178.59ZM125.62,194.06C100.5,194.06 76.71,188.4 55.46,178.28L51.43,186.74C73.92,197.45 99.08,203.44 125.62,203.44V194.06ZM206.64,157.04C208.19,165.89 203.45,174.67 195.13,178.59L199.12,187.07C211.07,181.45 218.2,168.64 215.87,155.41L206.64,157.04ZM34.82,155.06C32.45,168.26 39.52,181.07 51.43,186.74L55.46,178.28C47.16,174.33 42.46,165.54 44.04,156.71L34.82,155.06Z" android:pathData="M95.07,121.28L82.11,134.24L112.95,165.08C118.32,161.99 122.85,157.57 126.06,152.26L95.07,121.28Z"
android:fillColor="#000000"/> android:fillColor="#000000"/>
<path <path
android:pathData="M71.48,170.76C63.34,167.44 58.52,158.79 61.34,150.47C70.03,124.82 95.38,106.25 125.31,106.25C155.24,106.25 180.59,124.82 189.29,150.47C192.1,158.79 187.28,167.44 179.14,170.76C162.53,177.52 144.36,181.25 125.31,181.25C106.26,181.25 88.09,177.52 71.48,170.76Z" android:pathData="M394.79,96.61C374.95,96.28 357.68,113.06 357.49,132.9C357.41,140.15 359.45,146.93 363.03,152.66L394.39,121.29L407.36,134.26L376.3,165.3C382.44,168.73 389.68,170.45 397.35,169.8C415.01,168.3 429.27,153.98 430.7,136.32C432.42,114.9 415.73,96.96 394.79,96.61ZM363.03,152.66L348.73,166.94C353.43,170.82 357.94,174.97 362.27,179.33L376.3,165.3C370.87,162.28 366.3,157.91 363.03,152.66ZM422.09,298.44C413.84,251.46 392.38,209.71 362.27,179.33L355.79,185.81C351.44,181.43 346.92,177.31 342.22,173.47L348.73,166.94C319.53,142.71 283.85,128.37 245.01,128.37C206.16,128.37 170.25,142.8 140.99,167.2L147.51,173.71C142.81,177.57 138.3,181.71 133.97,186.1L127.49,179.62C97.66,209.82 76.37,251.19 68.04,297.74C63.44,323.55 77.25,348.61 100.54,359.7C144.51,380.63 193.71,392.35 245.63,392.35C297.54,392.35 345.72,380.87 389.35,360.35C412.72,349.34 426.65,324.29 422.09,298.44ZM381.54,343.75C340.31,363.17 294.24,374.01 245.63,374.01C197.01,374.01 149.99,362.94 108.42,343.15C92.2,335.42 83,318.23 86.09,300.97C93.86,257.51 113.49,219.81 140.45,192.57L154.03,180.24C180.14,158.99 211.55,146.69 245.01,146.69C278.46,146.69 309.63,158.88 335.69,179.98L349.31,192.29C376.54,219.69 396.33,257.73 404.04,301.61C407.08,318.92 397.82,336.11 381.54,343.75ZM126.08,152.28C122.87,157.58 118.34,161.99 112.96,165.09L127.49,179.62C131.82,175.23 136.31,171.09 140.99,167.2L126.08,152.28ZM126.08,152.28C129.56,146.54 131.51,139.77 131.38,132.56C130.98,112.5 114.04,96.21 93.97,96.61C73.05,97.03 56.44,114.95 58.17,136.32C59.61,154.07 74.01,168.43 91.76,169.82C99.51,170.42 106.8,168.61 112.96,165.09L82.13,134.26L95.09,121.29L126.08,152.28Z"
android:fillColor="#000000" android:fillColor="#000000"
android:fillType="evenOdd"/> android:fillType="evenOdd"/>
<path <path
android:pathData="M131.25,146.25a15.62,12.5 0,1 0,31.25 0a15.62,12.5 0,1 0,-31.25 0z" android:pathData="M407.36,134.26L376.3,165.3C370.87,162.28 366.3,157.91 363.03,152.66L394.39,121.29L407.36,134.26Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<path
android:pathData="M362.27,179.33L355.79,185.81C351.44,181.43 346.92,177.32 342.22,173.47L348.73,166.94C353.43,170.82 357.94,174.97 362.27,179.33Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<path
android:pathData="M147.51,173.71C142.81,177.57 138.3,181.71 133.97,186.1L127.49,179.62C131.82,175.23 136.31,171.09 140.99,167.2L147.51,173.71Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<path
android:pathData="M126.08,152.28C122.87,157.58 118.34,161.99 112.96,165.09L82.13,134.26L95.09,121.29L126.08,152.28Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<path
android:pathData="M407.36,134.24L376.3,165.29C370.87,162.26 366.3,157.9 363.03,152.64L394.39,121.28L407.36,134.24Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<path
android:pathData="M348.73,166.93L342.2,173.45C346.9,177.3 351.43,181.43 355.77,185.81L362.26,179.33C357.92,174.95 353.41,170.82 348.73,166.94V166.93Z"
android:fillColor="#000000"/>
<path
android:pathData="M140.98,167.18C136.3,171.07 131.8,175.23 127.47,179.6L133.95,186.08C138.28,181.69 142.81,177.57 147.49,173.7L140.98,167.18Z"
android:fillColor="#000000"/>
<path
android:pathData="M140.37,329.06C124.47,322.58 115.04,305.67 120.55,289.4C137.54,239.24 187.12,202.92 245.64,202.92C304.16,202.92 353.74,239.23 370.75,289.4C376.27,305.67 366.83,322.6 350.93,329.06C318.44,342.3 282.9,349.58 245.66,349.58C208.42,349.58 172.87,342.3 140.39,329.06H140.37Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<path
android:pathData="M287.82,305.59C304.7,305.59 318.38,294.65 318.38,281.15C318.38,267.65 304.7,256.7 287.82,256.7C270.94,256.7 257.26,267.65 257.26,281.15C257.26,294.65 270.94,305.59 287.82,305.59Z"
android:fillColor="#ffffff"/> android:fillColor="#ffffff"/>
<path <path
android:pathData="M86.52,146.26a15.62,12.5 0,1 0,31.25 0a15.62,12.5 0,1 0,-31.25 0z" android:pathData="M200.35,305.61C217.22,305.61 230.91,294.66 230.91,281.16C230.91,267.66 217.22,256.72 200.35,256.72C183.47,256.72 169.78,267.66 169.78,281.16C169.78,294.66 183.47,305.61 200.35,305.61Z"
android:fillColor="#ffffff"/> android:fillColor="#ffffff"/>
</vector> </vector>