feat: revamp about screen

Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
Infi 2023-07-04 18:24:28 +02:00
parent 98aa10c39e
commit 5981f064b5
7 changed files with 140 additions and 24 deletions

View File

@ -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"

View File

@ -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)
}
} }

View File

@ -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)

View File

@ -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))
}
}
} }
} }
} }

View File

@ -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>

View File

@ -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>

View File

@ -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>