feat: revamp about screen
Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
parent
98aa10c39e
commit
5981f064b5
|
|
@ -180,6 +180,7 @@ dependencies {
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha10"
|
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha10"
|
||||||
implementation 'com.github.MikeOrtiz:TouchImageView:3.3'
|
implementation 'com.github.MikeOrtiz:TouchImageView:3.3'
|
||||||
implementation "androidx.appcompat:appcompat:1.7.0-alpha02"
|
implementation "androidx.appcompat:appcompat:1.7.0-alpha02"
|
||||||
|
implementation 'com.google.android.material:material:1.9.0'
|
||||||
|
|
||||||
// hCaptcha - captcha provider
|
// hCaptcha - captcha provider
|
||||||
implementation "com.github.hcaptcha:hcaptcha-android-sdk:3.8.1"
|
implementation "com.github.hcaptcha:hcaptcha-android-sdk:3.8.1"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
package chat.revolt
|
package chat.revolt
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import com.google.android.material.color.DynamicColors
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
class RevoltApplication : Application() {
|
class RevoltApplication : Application() {
|
||||||
|
init {
|
||||||
|
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -28,6 +28,7 @@ import io.ktor.serialization.kotlinx.json.json
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -108,7 +109,7 @@ object RevoltAPI {
|
||||||
var sessionToken: String = ""
|
var sessionToken: String = ""
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class)
|
||||||
val realtimeContext = newSingleThreadContext("RealtimeContext")
|
val realtimeContext = newSingleThreadContext("RealtimeContext")
|
||||||
val wsFrameChannel = Channel<Any>(Channel.UNLIMITED)
|
val wsFrameChannel = Channel<Any>(Channel.UNLIMITED)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,32 +2,45 @@ package chat.revolt.screens.about
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
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.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ElevatedButton
|
import androidx.compose.material3.ElevatedButton
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
|
import androidx.compose.material3.TabRow
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.platform.ClipboardManager
|
import androidx.compose.ui.platform.ClipboardManager
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
|
@ -38,23 +51,24 @@ import chat.revolt.api.REVOLT_BASE
|
||||||
import chat.revolt.api.RevoltJson
|
import chat.revolt.api.RevoltJson
|
||||||
import chat.revolt.api.routes.misc.Root
|
import chat.revolt.api.routes.misc.Root
|
||||||
import chat.revolt.api.routes.misc.getRootRoute
|
import chat.revolt.api.routes.misc.getRootRoute
|
||||||
|
import chat.revolt.api.settings.GlobalState
|
||||||
import chat.revolt.components.generic.PageHeader
|
import chat.revolt.components.generic.PageHeader
|
||||||
|
import chat.revolt.ui.theme.Theme
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
class AboutViewModel(
|
class AboutViewModel(
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _root = mutableStateOf<Root?>(null)
|
var root by mutableStateOf<Root?>(null)
|
||||||
val root: State<Root?>
|
var selectedTabIndex by mutableStateOf(0)
|
||||||
get() = _root
|
|
||||||
|
|
||||||
fun getDebugInformation(): Map<String, String> {
|
fun getDebugInformation(): Map<String, String> {
|
||||||
return mapOf(
|
return mapOf(
|
||||||
"App ID" to BuildConfig.APPLICATION_ID,
|
"App ID" to BuildConfig.APPLICATION_ID,
|
||||||
"App Version" to BuildConfig.VERSION_NAME,
|
"App Version" to BuildConfig.VERSION_NAME,
|
||||||
"API Host" to URI(REVOLT_BASE).host,
|
"API Host" to URI(REVOLT_BASE).host,
|
||||||
"API Version" to (root.value?.revolt ?: "Unknown"),
|
"API Version" to (root?.revolt ?: "Unknown"),
|
||||||
"Runtime SDK" to Build.VERSION.SDK_INT.toString(),
|
"Runtime SDK" to Build.VERSION.SDK_INT.toString(),
|
||||||
"Model" to "${Build.MANUFACTURER} ${
|
"Model" to "${Build.MANUFACTURER} ${
|
||||||
Build.DEVICE.replaceFirstChar {
|
Build.DEVICE.replaceFirstChar {
|
||||||
|
|
@ -66,7 +80,7 @@ class AboutViewModel(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_root.value = getRootRoute().copy()
|
root = getRootRoute().copy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -149,6 +163,63 @@ fun AboutScreen(
|
||||||
showBackButton = true,
|
showBackButton = true,
|
||||||
onBackButtonClicked = { navController.popBackStack() })
|
onBackButtonClicked = { navController.popBackStack() })
|
||||||
|
|
||||||
|
// TODO this should be a reusable "tabs" component
|
||||||
|
when (GlobalState.theme) {
|
||||||
|
Theme.M3Dynamic -> AndroidView(
|
||||||
|
factory = {
|
||||||
|
com.google.android.material.tabs.TabLayout(it).apply {
|
||||||
|
tabMode = com.google.android.material.tabs.TabLayout.MODE_FIXED
|
||||||
|
tabGravity = com.google.android.material.tabs.TabLayout.GRAVITY_FILL
|
||||||
|
|
||||||
|
addTab(newTab().setText(it.getString(R.string.about_tab_version)))
|
||||||
|
addTab(newTab().setText(it.getString(R.string.about_tab_details)))
|
||||||
|
|
||||||
|
addOnTabSelectedListener(object :
|
||||||
|
com.google.android.material.tabs.TabLayout.OnTabSelectedListener {
|
||||||
|
override fun onTabSelected(tab: com.google.android.material.tabs.TabLayout.Tab?) {
|
||||||
|
viewModel.selectedTabIndex = tab?.position ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTabUnselected(tab: com.google.android.material.tabs.TabLayout.Tab?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTabReselected(tab: com.google.android.material.tabs.TabLayout.Tab?) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update = {
|
||||||
|
it.getTabAt(viewModel.selectedTabIndex)?.select()
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> TabRow(selectedTabIndex = viewModel.selectedTabIndex) {
|
||||||
|
Tab(
|
||||||
|
selected = viewModel.selectedTabIndex == 0,
|
||||||
|
onClick = { viewModel.selectedTabIndex = 0 },
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.about_tab_version),
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Tab(
|
||||||
|
selected = viewModel.selectedTabIndex == 1,
|
||||||
|
onClick = { viewModel.selectedTabIndex = 1 },
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.about_tab_details),
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
@ -157,22 +228,58 @@ fun AboutScreen(
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
|
if (viewModel.root == null) {
|
||||||
if (viewModel.root.value == null) {
|
CircularProgressIndicator(
|
||||||
Text(
|
modifier = Modifier
|
||||||
text = stringResource(R.string.loading),
|
.size(48.dp)
|
||||||
color = MaterialTheme.colorScheme.onBackground.copy(
|
|
||||||
alpha = 0.5f
|
|
||||||
),
|
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
fontWeight = FontWeight.Normal
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
DebugInfo(viewModel)
|
when (viewModel.selectedTabIndex) {
|
||||||
TextButton(onClick = ::copyDebugInformation) {
|
0 -> {
|
||||||
Text(text = stringResource(id = R.string.copy))
|
Image(
|
||||||
|
painter = painterResource(R.drawable.revolt_logo_wide),
|
||||||
|
contentDescription = stringResource(R.string.about_full_name),
|
||||||
|
colorFilter = ColorFilter.tint(LocalContentColor.current)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.about_full_name),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = BuildConfig.VERSION_NAME,
|
||||||
|
style = MaterialTheme.typography.labelMedium.copy(
|
||||||
|
fontWeight = FontWeight.Normal
|
||||||
|
),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.about_brought_to_you_by),
|
||||||
|
style = MaterialTheme.typography.labelSmall.copy(
|
||||||
|
fontWeight = FontWeight.Light
|
||||||
|
),
|
||||||
|
color = LocalContentColor.current.copy(
|
||||||
|
alpha = 0.5f
|
||||||
|
),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
1 -> {
|
||||||
|
DebugInfo(viewModel)
|
||||||
|
TextButton(onClick = ::copyDebugInformation) {
|
||||||
|
Text(text = stringResource(id = R.string.copy))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Theme.Revolt" parent="Theme.AppCompat.DayNight.NoActionBar">
|
<style name="Theme.Revolt" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
<item name="android:windowSplashScreenBackground">@color/background</item>
|
<item name="android:windowSplashScreenBackground">@color/background</item>
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,10 @@
|
||||||
<string name="mfa_password_lead">Enter your password to continue.</string>
|
<string name="mfa_password_lead">Enter your password to continue.</string>
|
||||||
|
|
||||||
<string name="about">About</string>
|
<string name="about">About</string>
|
||||||
<string name="app_full_name">Revolt on Android</string>
|
<string name="about_full_name">Revolt on Android</string>
|
||||||
|
<string name="about_brought_to_you_by">Brought to you with ❤ by the Revolt team.</string>
|
||||||
|
<string name="about_tab_version">Version</string>
|
||||||
|
<string name="about_tab_details">Details</string>
|
||||||
|
|
||||||
<string name="oss_attribution">OSS Attribution</string>
|
<string name="oss_attribution">OSS Attribution</string>
|
||||||
<string name="oss_attribution_body">Revolt is built with the help of these awesome open-source projects.</string>
|
<string name="oss_attribution_body">Revolt is built with the help of these awesome open-source projects.</string>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Theme.Revolt" parent="Theme.AppCompat.DayNight.NoActionBar">
|
<style name="Theme.Revolt" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue