mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-27 02:07:42 +00:00
Add LoadingActivity
This commit is contained in:
parent
0bc7b4dc4c
commit
d159a0bcab
@ -109,6 +109,11 @@
|
|||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
||||||
|
<activity
|
||||||
|
android:name="org.thoughtcrime.securesms.onboarding.LoadingActivity"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
||||||
<activity
|
<activity
|
||||||
android:name="org.thoughtcrime.securesms.onboarding.DisplayNameActivity"
|
android:name="org.thoughtcrime.securesms.onboarding.DisplayNameActivity"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package org.thoughtcrime.securesms.onboarding
|
package org.thoughtcrime.securesms.onboarding
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -10,54 +9,28 @@ import android.view.ViewGroup
|
|||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentPagerAdapter
|
import androidx.fragment.app.FragmentPagerAdapter
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.filter
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import network.loki.messenger.databinding.ActivityLinkDeviceBinding
|
import network.loki.messenger.databinding.ActivityLinkDeviceBinding
|
||||||
import network.loki.messenger.databinding.FragmentRecoveryPhraseBinding
|
import network.loki.messenger.databinding.FragmentRecoveryPhraseBinding
|
||||||
import org.session.libsession.snode.SnodeModule
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsignal.crypto.MnemonicCodec
|
import org.session.libsignal.crypto.MnemonicCodec
|
||||||
import org.session.libsignal.database.LokiAPIDatabaseProtocol
|
|
||||||
import org.session.libsignal.utilities.Hex
|
import org.session.libsignal.utilities.Hex
|
||||||
import org.session.libsignal.utilities.KeyHelper
|
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.hexEncodedPublicKey
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
|
||||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||||
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
|
||||||
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
|
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
|
||||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
|
||||||
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragment
|
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragment
|
||||||
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragmentDelegate
|
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragmentDelegate
|
||||||
import org.thoughtcrime.securesms.util.push
|
|
||||||
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
|
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
|
class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var configFactory: ConfigFactory
|
|
||||||
|
|
||||||
private lateinit var binding: ActivityLinkDeviceBinding
|
private lateinit var binding: ActivityLinkDeviceBinding
|
||||||
internal val database: LokiAPIDatabaseProtocol
|
|
||||||
get() = SnodeModule.shared.storage
|
|
||||||
private val adapter = LinkDeviceActivityAdapter(this)
|
|
||||||
private var restoreJob: Job? = null
|
|
||||||
|
|
||||||
override fun onBackPressed() {
|
private val adapter = LinkDeviceActivityAdapter(this)
|
||||||
if (restoreJob?.isActive == true) return // Don't allow going back with a pending job
|
|
||||||
super.onBackPressed()
|
|
||||||
}
|
|
||||||
|
|
||||||
// region Lifecycle
|
// region Lifecycle
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -106,58 +79,7 @@ class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDel
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun continueWithSeed(seed: ByteArray) {
|
private fun continueWithSeed(seed: ByteArray) {
|
||||||
|
startLoadingActivity(seed)
|
||||||
// only have one sync job running at a time (prevent QR from trying to spawn a new job)
|
|
||||||
if (restoreJob?.isActive == true) return
|
|
||||||
|
|
||||||
restoreJob = lifecycleScope.launch {
|
|
||||||
// This is here to resolve a case where the app restarts before a user completes onboarding
|
|
||||||
// which can result in an invalid database state
|
|
||||||
database.clearAllLastMessageHashes()
|
|
||||||
database.clearReceivedMessageHashValues()
|
|
||||||
|
|
||||||
// RestoreActivity handles seed this way
|
|
||||||
val keyPairGenerationResult = KeyPairUtilities.generate(seed)
|
|
||||||
val x25519KeyPair = keyPairGenerationResult.x25519KeyPair
|
|
||||||
KeyPairUtilities.store(this@LinkDeviceActivity, seed, keyPairGenerationResult.ed25519KeyPair, x25519KeyPair)
|
|
||||||
configFactory.keyPairChanged()
|
|
||||||
val userHexEncodedPublicKey = x25519KeyPair.hexEncodedPublicKey
|
|
||||||
val registrationID = KeyHelper.generateRegistrationId(false)
|
|
||||||
TextSecurePreferences.setLocalRegistrationId(this@LinkDeviceActivity, registrationID)
|
|
||||||
TextSecurePreferences.setLocalNumber(this@LinkDeviceActivity, userHexEncodedPublicKey)
|
|
||||||
TextSecurePreferences.setRestorationTime(this@LinkDeviceActivity, System.currentTimeMillis())
|
|
||||||
TextSecurePreferences.setHasViewedSeed(this@LinkDeviceActivity, true)
|
|
||||||
|
|
||||||
binding.loader.isVisible = true
|
|
||||||
val snackBar = Snackbar.make(binding.containerLayout, R.string.activity_link_device_skip_prompt,Snackbar.LENGTH_INDEFINITE)
|
|
||||||
.setAction(R.string.registration_activity__skip) { register(true) }
|
|
||||||
|
|
||||||
val skipJob = launch {
|
|
||||||
delay(15_000L)
|
|
||||||
snackBar.show()
|
|
||||||
}
|
|
||||||
// start polling and wait for updated message
|
|
||||||
ApplicationContext.getInstance(this@LinkDeviceActivity).apply {
|
|
||||||
startPollingIfNeeded()
|
|
||||||
}
|
|
||||||
TextSecurePreferences.events.filter { it == TextSecurePreferences.CONFIGURATION_SYNCED }.collect {
|
|
||||||
// handle we've synced
|
|
||||||
snackBar.dismiss()
|
|
||||||
skipJob.cancel()
|
|
||||||
register(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.loader.isVisible = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun register(skipped: Boolean) {
|
|
||||||
restoreJob?.cancel()
|
|
||||||
binding.loader.isVisible = false
|
|
||||||
TextSecurePreferences.setLastConfigurationSyncTime(this, System.currentTimeMillis())
|
|
||||||
val intent = Intent(this@LinkDeviceActivity, if (skipped) DisplayNameActivity::class.java else PNModeActivity::class.java)
|
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
|
||||||
push(intent)
|
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,106 @@
|
|||||||
|
package org.thoughtcrime.securesms.onboarding
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.TweenSpec
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.ComposeView
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
|
import org.thoughtcrime.securesms.ui.AppTheme
|
||||||
|
import org.thoughtcrime.securesms.ui.ProgressArc
|
||||||
|
import org.thoughtcrime.securesms.util.push
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val EXTRA_MNEMONIC = "mnemonic"
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class LoadingActivity: BaseActionBarActivity() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var configFactory: ConfigFactory
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var prefs: TextSecurePreferences
|
||||||
|
|
||||||
|
private val viewModel: LoadingViewModel by viewModels()
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
|
override fun onBackPressed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun register(skipped: Boolean) {
|
||||||
|
prefs.setLastConfigurationSyncTime(System.currentTimeMillis())
|
||||||
|
Intent(this, if (skipped) DisplayNameActivity::class.java else PNModeActivity::class.java)
|
||||||
|
.apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK }
|
||||||
|
.also(::push)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
ComposeView(this)
|
||||||
|
.apply { setContent { LoadingScreen() } }
|
||||||
|
.let(::setContentView)
|
||||||
|
|
||||||
|
viewModel.restore(application, intent.getByteArrayExtra(EXTRA_MNEMONIC)!!)
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.eventFlow.collect {
|
||||||
|
when (it) {
|
||||||
|
Event.TIMEOUT -> register(skipped = true)
|
||||||
|
Event.SUCCESS -> register(skipped = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoadingScreen() {
|
||||||
|
val state by viewModel.stateFlow.collectAsState()
|
||||||
|
|
||||||
|
val animatable = remember { Animatable(initialValue = 0f, visibilityThreshold = 0.005f) }
|
||||||
|
|
||||||
|
LaunchedEffect(state) {
|
||||||
|
animatable.stop()
|
||||||
|
animatable.animateTo(
|
||||||
|
targetValue = 1f,
|
||||||
|
animationSpec = TweenSpec(durationMillis = state.duration.inWholeMilliseconds.toInt())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppTheme {
|
||||||
|
Column {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
ProgressArc(animatable.value, modifier = Modifier.align(Alignment.CenterHorizontally))
|
||||||
|
Text("One moment please..", modifier = Modifier.align(Alignment.CenterHorizontally), style = MaterialTheme.typography.h6)
|
||||||
|
Text("Loading your account", modifier = Modifier.align(Alignment.CenterHorizontally))
|
||||||
|
Spacer(modifier = Modifier.weight(2f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.startLoadingActivity(mnemonic: ByteArray) {
|
||||||
|
Intent(this, LoadingActivity::class.java)
|
||||||
|
.apply { putExtra(EXTRA_MNEMONIC, mnemonic) }
|
||||||
|
.also(::startActivity)
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
package org.thoughtcrime.securesms.onboarding
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.session.libsession.snode.SnodeModule
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
import org.session.libsignal.database.LokiAPIDatabaseProtocol
|
||||||
|
import org.session.libsignal.utilities.KeyHelper
|
||||||
|
import org.session.libsignal.utilities.hexEncodedPublicKey
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.hours
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
data class State(val duration: Duration)
|
||||||
|
|
||||||
|
private val DONE_TIME = 1.seconds
|
||||||
|
private val DONE_ANIMATE_TIME = 500.milliseconds
|
||||||
|
|
||||||
|
private val TOTAL_ANIMATE_TIME = 14.seconds
|
||||||
|
private val TOTAL_TIME = 15.seconds
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class LoadingViewModel @Inject constructor(
|
||||||
|
private val configFactory: ConfigFactory,
|
||||||
|
private val prefs: TextSecurePreferences,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val state = MutableStateFlow(State(TOTAL_ANIMATE_TIME))
|
||||||
|
val stateFlow = state.asStateFlow()
|
||||||
|
|
||||||
|
private val event = Channel<Event>()
|
||||||
|
val eventFlow = event.receiveAsFlow()
|
||||||
|
|
||||||
|
private var restoreJob: Job? = null
|
||||||
|
|
||||||
|
internal val database: LokiAPIDatabaseProtocol
|
||||||
|
get() = SnodeModule.shared.storage
|
||||||
|
|
||||||
|
fun restore(context: Context, seed: ByteArray) {
|
||||||
|
|
||||||
|
// only have one sync job running at a time (prevent QR from trying to spawn a new job)
|
||||||
|
if (restoreJob?.isActive == true) return
|
||||||
|
|
||||||
|
restoreJob = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
// This is here to resolve a case where the app restarts before a user completes onboarding
|
||||||
|
// which can result in an invalid database state
|
||||||
|
database.clearAllLastMessageHashes()
|
||||||
|
database.clearReceivedMessageHashValues()
|
||||||
|
|
||||||
|
// RestoreActivity handles seed this way
|
||||||
|
val keyPairGenerationResult = KeyPairUtilities.generate(seed)
|
||||||
|
val x25519KeyPair = keyPairGenerationResult.x25519KeyPair
|
||||||
|
KeyPairUtilities.store(context, seed, keyPairGenerationResult.ed25519KeyPair, x25519KeyPair)
|
||||||
|
configFactory.keyPairChanged()
|
||||||
|
val userHexEncodedPublicKey = x25519KeyPair.hexEncodedPublicKey
|
||||||
|
val registrationID = KeyHelper.generateRegistrationId(false)
|
||||||
|
prefs.apply {
|
||||||
|
setLocalRegistrationId(registrationID)
|
||||||
|
setLocalNumber(userHexEncodedPublicKey)
|
||||||
|
setRestorationTime(System.currentTimeMillis())
|
||||||
|
setHasViewedSeed(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val skipJob = launch(Dispatchers.IO) {
|
||||||
|
delay(TOTAL_TIME)
|
||||||
|
event.send(Event.TIMEOUT)
|
||||||
|
}
|
||||||
|
|
||||||
|
// start polling and wait for updated message
|
||||||
|
ApplicationContext.getInstance(context).apply { startPollingIfNeeded() }
|
||||||
|
TextSecurePreferences.events.filter { it == TextSecurePreferences.CONFIGURATION_SYNCED }.collect {
|
||||||
|
// handle we've synced
|
||||||
|
skipJob.cancel()
|
||||||
|
|
||||||
|
state.value = State(DONE_ANIMATE_TIME)
|
||||||
|
delay(DONE_TIME)
|
||||||
|
event.send(Event.SUCCESS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface Event {
|
||||||
|
object SUCCESS: Event
|
||||||
|
object TIMEOUT: Event
|
||||||
|
}
|
@ -42,6 +42,7 @@ const val oceanLight5 = 0xffE7F3F4
|
|||||||
const val oceanLight6 = 0xffECFAFB
|
const val oceanLight6 = 0xffECFAFB
|
||||||
const val oceanLight7 = 0xffFCFFFF
|
const val oceanLight7 = 0xffFCFFFF
|
||||||
|
|
||||||
|
val session_accent = Color(0xFF31F196)
|
||||||
val ocean_accent = Color(0xff57C9FA)
|
val ocean_accent = Color(0xff57C9FA)
|
||||||
|
|
||||||
val oceanLights = arrayOf(oceanLight0, oceanLight1, oceanLight2, oceanLight3, oceanLight4, oceanLight5, oceanLight6, oceanLight7)
|
val oceanLights = arrayOf(oceanLight0, oceanLight1, oceanLight2, oceanLight3, oceanLight4, oceanLight5, oceanLight6, oceanLight7)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.ui
|
package org.thoughtcrime.securesms.ui
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
@ -10,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
@ -26,9 +28,13 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.RectangleShape
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
@ -37,6 +43,7 @@ import kotlinx.coroutines.launch
|
|||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.components.ProfilePictureView
|
import org.thoughtcrime.securesms.components.ProfilePictureView
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ItemButton(
|
fun ItemButton(
|
||||||
@ -180,3 +187,49 @@ fun RowScope.Avatar(recipient: Recipient) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProgressArc(progress: Float, modifier: Modifier = Modifier) {
|
||||||
|
val text = (progress * 100).roundToInt()
|
||||||
|
|
||||||
|
Box(modifier = modifier) {
|
||||||
|
Arc(percentage = progress, modifier = Modifier.align(Alignment.Center))
|
||||||
|
Text("${text}%", color = Color.White, modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.h2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Arc(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
percentage: Float = 0.25f,
|
||||||
|
fillColor: Color = session_accent,
|
||||||
|
backgroundColor: Color = classicDarkColors[3],
|
||||||
|
strokeWidth: Dp = 18.dp,
|
||||||
|
sweepAngle: Float = 310f,
|
||||||
|
startAngle: Float = (360f - sweepAngle) / 2 + 90f
|
||||||
|
) {
|
||||||
|
Canvas(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(strokeWidth)
|
||||||
|
.size(186.dp)
|
||||||
|
) {
|
||||||
|
// Background Line
|
||||||
|
drawArc(
|
||||||
|
color = backgroundColor,
|
||||||
|
startAngle,
|
||||||
|
sweepAngle,
|
||||||
|
false,
|
||||||
|
style = Stroke(strokeWidth.toPx(), cap = StrokeCap.Round),
|
||||||
|
size = Size(size.width, size.height)
|
||||||
|
)
|
||||||
|
|
||||||
|
drawArc(
|
||||||
|
color = fillColor,
|
||||||
|
startAngle,
|
||||||
|
percentage * sweepAngle,
|
||||||
|
false,
|
||||||
|
style = Stroke(strokeWidth.toPx(), cap = StrokeCap.Round),
|
||||||
|
size = Size(size.width, size.height)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:id="@+id/containerLayout"
|
android:id="@+id/containerLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
@ -18,28 +17,4 @@
|
|||||||
|
|
||||||
</androidx.viewpager.widget.ViewPager>
|
</androidx.viewpager.widget.ViewPager>
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:animateLayoutChanges="true">
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/loader"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="#A4000000"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<com.github.ybq.android.spinkit.SpinKitView
|
|
||||||
style="@style/SpinKitView.Large.ThreeBounce"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
app:SpinKit_Color="@android:color/white" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
Loading…
x
Reference in New Issue
Block a user