feat: add feedback dialog
This commit is contained in:
parent
9f6d184d18
commit
576efa01ad
|
|
@ -64,10 +64,14 @@ android {
|
|||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
buildConfigField "String", "SENTRY_DSN", "\"${property('revoltbuild.properties', 'sentry.dsn', 'RVX_SENTRY_DSN')}\""
|
||||
buildConfigField "Boolean", "ANALYSIS_ENABLED", "${property('revoltbuild.properties', 'analysis.enabled', 'RVX_ANALYSIS_ENABLED')}"
|
||||
buildConfigField "String", "ANALYSIS_BASEURL", "\"${property('revoltbuild.properties', 'analysis.base_url', 'RVX_ANALYSIS_BASEURL')}\""
|
||||
}
|
||||
|
||||
debug {
|
||||
buildConfigField "String", "SENTRY_DSN", "\"${property('revoltbuild.properties', 'sentry.dsn', 'RVX_SENTRY_DSN')}\""
|
||||
buildConfigField "Boolean", "ANALYSIS_ENABLED", "${property('revoltbuild.properties', 'analysis.enabled', 'RVX_ANALYSIS_ENABLED')}"
|
||||
buildConfigField "String", "ANALYSIS_BASEURL", "\"${property('revoltbuild.properties', 'analysis.base_url', 'RVX_ANALYSIS_BASEURL')}\""
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
|
|
@ -112,9 +116,9 @@ dependencies {
|
|||
implementation 'androidx.compose.material:material'
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
implementation "androidx.compose.ui:ui-tooling-preview"
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.0'
|
||||
implementation 'androidx.activity:activity-compose:1.6.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1'
|
||||
implementation 'androidx.activity:activity-compose:1.7.0'
|
||||
|
||||
// Accompanist - Jetpack Compose Extensions
|
||||
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.navigation.compose.dialog
|
||||
import chat.revolt.api.settings.GlobalState
|
||||
import chat.revolt.screens.SplashScreen
|
||||
import chat.revolt.screens.about.AboutScreen
|
||||
import chat.revolt.screens.about.AttributionScreen
|
||||
import chat.revolt.screens.about.PlaceholderScreen
|
||||
import chat.revolt.screens.chat.ChatRouterScreen
|
||||
import chat.revolt.screens.chat.dialogs.FeedbackDialog
|
||||
import chat.revolt.screens.login.GreeterScreen
|
||||
import chat.revolt.screens.login.LoginScreen
|
||||
import chat.revolt.screens.login.MfaScreen
|
||||
|
|
@ -111,6 +113,7 @@ fun AppEntrypoint() {
|
|||
|
||||
composable("settings") { SettingsScreen(navController) }
|
||||
composable("settings/appearance") { AppearanceSettingsScreen(navController) }
|
||||
dialog("settings/feedback") { FeedbackDialog(navController) }
|
||||
|
||||
composable("about") { AboutScreen(navController) }
|
||||
composable("about/oss") { AttributionScreen(navController) }
|
||||
|
|
|
|||
|
|
@ -4,18 +4,27 @@ import android.os.Handler
|
|||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import chat.revolt.BuildConfig
|
||||
import chat.revolt.api.realtime.DisconnectionState
|
||||
import chat.revolt.api.realtime.RealtimeSocket
|
||||
import chat.revolt.api.routes.user.fetchSelf
|
||||
import chat.revolt.api.schemas.*
|
||||
import chat.revolt.api.schemas.Channel
|
||||
import chat.revolt.api.schemas.Emoji
|
||||
import chat.revolt.api.schemas.Message
|
||||
import chat.revolt.api.schemas.Server
|
||||
import chat.revolt.api.schemas.User
|
||||
import chat.revolt.api.unreads.Unreads
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.okhttp.*
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.plugins.logging.*
|
||||
import io.ktor.client.plugins.websocket.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.okhttp.OkHttp
|
||||
import io.ktor.client.plugins.DefaultRequest
|
||||
import io.ktor.client.plugins.HttpRequestRetry
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.client.plugins.defaultRequest
|
||||
import io.ktor.client.plugins.logging.LogLevel
|
||||
import io.ktor.client.plugins.logging.Logging
|
||||
import io.ktor.client.plugins.websocket.WebSockets
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
|
|
@ -61,6 +70,10 @@ val RevoltHttp = HttpClient(OkHttp) {
|
|||
|
||||
defaultRequest {
|
||||
url(REVOLT_BASE)
|
||||
header(
|
||||
"User-Agent",
|
||||
"Ktor RevoltAndroid/${BuildConfig.VERSION_NAME} (Android ${android.os.Build.VERSION.SDK_INT}; ${android.os.Build.MANUFACTURER} ${android.os.Build.DEVICE})"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
|
||||
/**
|
||||
* Convenience wrapper around [TextField] that sets the [KeyboardOptions] and [VisualTransformation]
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun FormTextField(
|
||||
|
|
@ -18,14 +21,19 @@ fun FormTextField(
|
|||
onChange: (it: String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
type: KeyboardType = KeyboardType.Text,
|
||||
supportingText: @Composable (() -> Unit)? = null,
|
||||
singleLine: Boolean = true,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
TextField(
|
||||
value = value,
|
||||
onValueChange = onChange,
|
||||
singleLine = true,
|
||||
singleLine = singleLine,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = type),
|
||||
visualTransformation = if (type == KeyboardType.Password) PasswordVisualTransformation() else VisualTransformation.None,
|
||||
label = { Text(label) },
|
||||
supportingText = supportingText,
|
||||
enabled = enabled,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
package chat.revolt.screens.chat.dialogs
|
||||
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconToggleButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import chat.revolt.BuildConfig
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.REVOLT_BASE
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.RevoltHttp
|
||||
import chat.revolt.components.generic.FormTextField
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.contentType
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
enum class FeedbackType(val value: String) {
|
||||
Satisfaction("satisfaction"),
|
||||
FeatureRequest("featurerequest"),
|
||||
BugReport("bug"),
|
||||
UxFeedback("ux"),
|
||||
Performance("performance"),
|
||||
Security("security"),
|
||||
Other("other")
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class FeedbackBody(
|
||||
val type: String,
|
||||
val message: String,
|
||||
val api_host: String,
|
||||
val app_id: String,
|
||||
val app_version: String,
|
||||
val app_build: String,
|
||||
val android_api: String,
|
||||
val android_device: String,
|
||||
val android_manufacturer: String,
|
||||
@SerialName("id_for_spam_protection_pls_dont_spam_but_if_you_do_i_will_know")
|
||||
val author: String
|
||||
)
|
||||
|
||||
suspend fun sendFeedback(type: FeedbackType, message: String): String {
|
||||
val response = RevoltHttp.post("${BuildConfig.ANALYSIS_BASEURL}/api/feedback/android") {
|
||||
setBody(
|
||||
FeedbackBody(
|
||||
type = type.value,
|
||||
message = message,
|
||||
api_host = REVOLT_BASE,
|
||||
app_id = BuildConfig.APPLICATION_ID,
|
||||
app_version = BuildConfig.VERSION_NAME,
|
||||
app_build = BuildConfig.VERSION_CODE.toString(),
|
||||
android_api = android.os.Build.VERSION.SDK_INT.toString(),
|
||||
android_device = android.os.Build.DEVICE,
|
||||
android_manufacturer = android.os.Build.MANUFACTURER,
|
||||
author = RevoltAPI.selfId ?: "RevoltAPI.selfId is null"
|
||||
)
|
||||
)
|
||||
contentType(ContentType.Application.Json)
|
||||
}
|
||||
|
||||
Log.d("FeedbackDialog", "Feedback sent: ${response.bodyAsText()}")
|
||||
|
||||
return response.bodyAsText()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun FeedbackDialog(navController: NavController) {
|
||||
if (!BuildConfig.ANALYSIS_ENABLED) {
|
||||
AlertDialog(onDismissRequest = {
|
||||
navController.popBackStack()
|
||||
}, title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.settings_feedback_disabled_title),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}, text = {
|
||||
Text(text = stringResource(id = R.string.settings_feedback_disabled_message))
|
||||
}, confirmButton = {
|
||||
TextButton(onClick = {
|
||||
navController.popBackStack()
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.ok))
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
val category = mapOf(
|
||||
FeedbackType.Satisfaction to stringResource(R.string.settings_feedback_category_satisfaction),
|
||||
FeedbackType.FeatureRequest to stringResource(R.string.settings_feedback_category_feature),
|
||||
FeedbackType.BugReport to stringResource(R.string.settings_feedback_category_bug),
|
||||
FeedbackType.UxFeedback to stringResource(R.string.settings_feedback_category_ux),
|
||||
FeedbackType.Performance to stringResource(R.string.settings_feedback_category_performance),
|
||||
FeedbackType.Security to stringResource(R.string.settings_feedback_category_security),
|
||||
FeedbackType.Other to stringResource(R.string.settings_feedback_category_other)
|
||||
)
|
||||
val categoryDropdownExpanded = remember { mutableStateOf(false) }
|
||||
val categoryDropdownSelected = remember { mutableStateOf(FeedbackType.Satisfaction) }
|
||||
val message = remember { mutableStateOf("") }
|
||||
val error = remember { mutableStateOf("") }
|
||||
val sending = remember { mutableStateOf(false) }
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
navController.popBackStack()
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.settings_feedback),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(id = R.string.settings_feedback_introduction)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Box {
|
||||
TextField(
|
||||
value = category[categoryDropdownSelected.value]
|
||||
?: stringResource(id = R.string.unknown),
|
||||
onValueChange = {},
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.settings_feedback_category),
|
||||
)
|
||||
},
|
||||
readOnly = true,
|
||||
trailingIcon = {
|
||||
IconToggleButton(
|
||||
checked = categoryDropdownExpanded.value,
|
||||
onCheckedChange = {
|
||||
categoryDropdownExpanded.value = it
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowDropDown,
|
||||
contentDescription = stringResource(id = R.string.settings_feedback_category)
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
DropdownMenu(
|
||||
expanded = categoryDropdownExpanded.value,
|
||||
onDismissRequest = {
|
||||
categoryDropdownExpanded.value = false
|
||||
},
|
||||
) {
|
||||
category.forEach { (key, value) ->
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(text = value)
|
||||
},
|
||||
onClick = {
|
||||
categoryDropdownSelected.value = key
|
||||
categoryDropdownExpanded.value = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
FormTextField(
|
||||
value = message.value,
|
||||
label = stringResource(id = R.string.settings_feedback_message),
|
||||
onChange = {
|
||||
message.value = it
|
||||
},
|
||||
supportingText = {
|
||||
Text(
|
||||
text = "${message.value.length}/1250",
|
||||
)
|
||||
},
|
||||
enabled = !sending.value,
|
||||
singleLine = false,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
navController.popBackStack()
|
||||
},
|
||||
modifier = Modifier.testTag("feedback_cancel"),
|
||||
enabled = !sending.value,
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
if (message.value.length > 1250) {
|
||||
error.value =
|
||||
context.getString(R.string.settings_feedback_message_too_long, 1250)
|
||||
} else {
|
||||
error.value = ""
|
||||
sending.value = true
|
||||
scope.launch {
|
||||
try {
|
||||
val result =
|
||||
sendFeedback(categoryDropdownSelected.value, message.value)
|
||||
Log.d("FeedbackDialog", "Feedback sent with result: $result")
|
||||
|
||||
if (result.isBlank()) {
|
||||
navController.popBackStack()
|
||||
} else {
|
||||
error.value = result
|
||||
Toast.makeText(context, error.value, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("FeedbackDialog", "Error sending feedback", e)
|
||||
error.value = context.getString(R.string.settings_feedback_error)
|
||||
} finally {
|
||||
sending.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = !sending.value && message.value.isNotBlank(),
|
||||
modifier = Modifier.testTag("feedback_submit")
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.report_submit))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ import chat.revolt.components.chat.Message
|
|||
import chat.revolt.components.generic.FormTextField
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
enum class ReportingState {
|
||||
enum class ReportFlowState {
|
||||
Reason,
|
||||
Sending,
|
||||
Done,
|
||||
|
|
@ -52,13 +52,13 @@ fun ReportMessageDialog(
|
|||
val author = RevoltAPI.userCache[message.author]
|
||||
val messageIsBridged = author?.let { author.bot != null && message.masquerade != null } ?: false
|
||||
|
||||
val state = remember { mutableStateOf(ReportingState.Reason) }
|
||||
val state = remember { mutableStateOf(ReportFlowState.Reason) }
|
||||
|
||||
val selectedReason = remember { mutableStateOf("Illegal") }
|
||||
val userAddedContext = remember { mutableStateOf("") }
|
||||
|
||||
when (state.value) {
|
||||
ReportingState.Reason -> {
|
||||
ReportFlowState.Reason -> {
|
||||
val reasons = mapOf(
|
||||
"Illegal" to stringResource(id = R.string.report_reason_content_illegal),
|
||||
"PromotesHarm" to stringResource(id = R.string.report_reason_content_promotes_harm),
|
||||
|
|
@ -169,19 +169,18 @@ fun ReportMessageDialog(
|
|||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.report_reason_additional_hint),
|
||||
fontSize = 12.sp,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
FormTextField(
|
||||
value = userAddedContext.value,
|
||||
label = stringResource(id = R.string.report_reason_additional),
|
||||
onChange = {
|
||||
userAddedContext.value = it
|
||||
})
|
||||
},
|
||||
supportingText = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.report_reason_additional_hint)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
|
|
@ -197,7 +196,7 @@ fun ReportMessageDialog(
|
|||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
state.value = ReportingState.Sending
|
||||
state.value = ReportFlowState.Sending
|
||||
},
|
||||
modifier = Modifier.testTag("report_send")
|
||||
) {
|
||||
|
|
@ -207,7 +206,7 @@ fun ReportMessageDialog(
|
|||
)
|
||||
}
|
||||
|
||||
ReportingState.Sending -> {
|
||||
ReportFlowState.Sending -> {
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
title = {
|
||||
|
|
@ -231,9 +230,9 @@ fun ReportMessageDialog(
|
|||
ContentReportReason.valueOf(selectedReason.value),
|
||||
userAddedContext.value
|
||||
)
|
||||
state.value = ReportingState.Done
|
||||
state.value = ReportFlowState.Done
|
||||
} catch (e: Exception) {
|
||||
state.value = ReportingState.Error
|
||||
state.value = ReportFlowState.Error
|
||||
Log.e("ReportMessageDialog", "Failed to report message", e)
|
||||
return@launch
|
||||
}
|
||||
|
|
@ -246,7 +245,7 @@ fun ReportMessageDialog(
|
|||
)
|
||||
}
|
||||
|
||||
ReportingState.Done -> {
|
||||
ReportFlowState.Done -> {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
AlertDialog(
|
||||
|
|
@ -315,7 +314,7 @@ fun ReportMessageDialog(
|
|||
)
|
||||
}
|
||||
|
||||
ReportingState.Error -> {
|
||||
ReportFlowState.Error -> {
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
navController.popBackStack()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
package chat.revolt.screens.settings
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Build
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -73,6 +77,40 @@ fun SettingsScreen(
|
|||
) {
|
||||
navController.navigate("about")
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
SheetClickable(
|
||||
icon = { modifier ->
|
||||
Icon(
|
||||
imageVector = Icons.Default.Build,
|
||||
contentDescription = stringResource(id = R.string.settings_feedback),
|
||||
modifier = modifier
|
||||
)
|
||||
},
|
||||
label = { textStyle ->
|
||||
Text(text = stringResource(id = R.string.settings_feedback), style = textStyle)
|
||||
},
|
||||
modifier = Modifier.testTag("settings_view_feedback")
|
||||
) {
|
||||
navController.navigate("settings/feedback")
|
||||
}
|
||||
|
||||
SheetClickable(
|
||||
icon = { modifier ->
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = stringResource(id = R.string.logout),
|
||||
modifier = modifier
|
||||
)
|
||||
},
|
||||
label = { textStyle ->
|
||||
Text(text = stringResource(id = R.string.logout), style = textStyle)
|
||||
},
|
||||
modifier = Modifier.testTag("settings_view_logout")
|
||||
) {
|
||||
Toast.makeText(navController.context, "Not implemented yet", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -211,4 +211,21 @@
|
|||
<string name="settings_appearance_theme_m3dynamic">Material You</string>
|
||||
<string name="settings_appearance_theme_m3dynamic_unsupported">Material You (unsupported)</string>
|
||||
<string name="settings_appearance_theme_m3dynamic_unsupported_toast">Material You is not supported on this device.</string>
|
||||
|
||||
<string name="settings_feedback">Feedback</string>
|
||||
<string name="settings_feedback_introduction">Any feedback you have for Revolt is greatly appreciated and all feedback is read by the development team of our Android app.</string>
|
||||
<string name="settings_feedback_category">Category</string>
|
||||
<string name="settings_feedback_category_satisfaction">General-purpose feedback</string>
|
||||
<string name="settings_feedback_category_feature">Feature request</string>
|
||||
<string name="settings_feedback_category_bug">Bug report</string>
|
||||
<string name="settings_feedback_category_ux">User experience (UI/UX)</string>
|
||||
<string name="settings_feedback_category_performance">Performance</string>
|
||||
<string name="settings_feedback_category_security">Security</string>
|
||||
<string name="settings_feedback_category_other">Other</string>
|
||||
<string name="settings_feedback_message">Message</string>
|
||||
<string name="settings_feedback_message_too_long">Message is too long. Please shorten it to %1$d characters or less.</string>
|
||||
<string name="settings_feedback_error">An error occurred while sending your feedback. Please try again later.</string>
|
||||
|
||||
<string name="settings_feedback_disabled_title">Feedback unavailable</string>
|
||||
<string name="settings_feedback_disabled_message">Feedback is not available on this build of Revolt. Support for self-built versions is limited.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
sentry.dsn=
|
||||
sentry.dsn=
|
||||
analysis.enabled=false
|
||||
analysis.base_url=
|
||||
Loading…
Reference in New Issue