parent
2721d74c04
commit
a92a07e2ed
|
|
@ -79,10 +79,12 @@ import chat.revolt.api.HitRateLimitException
|
|||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.RevoltHttp
|
||||
import chat.revolt.api.api
|
||||
import chat.revolt.api.routes.microservices.geo.queryGeo
|
||||
import chat.revolt.api.routes.microservices.health.healthCheck
|
||||
import chat.revolt.api.routes.onboard.needsOnboarding
|
||||
import chat.revolt.api.schemas.HealthNotice
|
||||
import chat.revolt.api.settings.Experiments
|
||||
import chat.revolt.api.settings.GeoStateProvider
|
||||
import chat.revolt.api.settings.LoadedSettings
|
||||
import chat.revolt.api.settings.SyncedSettings
|
||||
import chat.revolt.composables.generic.HealthAlert
|
||||
|
|
@ -184,6 +186,17 @@ class MainActivityViewModel @Inject constructor(
|
|||
Experiments.hydrateWithKv()
|
||||
Log.d("MainActivity", "Performing health check")
|
||||
doHealthCheck()
|
||||
Log.d("MainActivity", "Performing update geo state")
|
||||
updateGeoState()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateGeoState() {
|
||||
try {
|
||||
Log.d("MainActivity", "Querying geo state")
|
||||
GeoStateProvider.updateGeoState(queryGeo())
|
||||
} catch (e: Exception) {
|
||||
Log.e("MainActivity", "Failed to query geo state", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package chat.revolt.api.routes.microservices.geo
|
||||
|
||||
import chat.revolt.api.HitRateLimitException
|
||||
import chat.revolt.api.RevoltHttp
|
||||
import chat.revolt.api.RevoltJson
|
||||
import chat.revolt.api.buildUserAgent
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GeoResponse(
|
||||
val countryCode: String,
|
||||
val isAgeRestrictedGeo: Boolean,
|
||||
)
|
||||
|
||||
suspend fun queryGeo(): GeoResponse {
|
||||
try {
|
||||
val response = RevoltHttp.get("https://geo.revolt.chat/?client=android") {
|
||||
header("User-Agent", buildUserAgent("Ktor queryGeo"))
|
||||
}
|
||||
|
||||
if (response.status == HttpStatusCode.OK) {
|
||||
return RevoltJson.decodeFromString(response.bodyAsText())
|
||||
} else throw Exception("Failed to query geo: ${response.status.value} ${response.status.description}")
|
||||
} catch (e: Exception) {
|
||||
throw Exception("Failed to query geo: ${e.message}", e).also {
|
||||
if (e is HitRateLimitException) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package chat.revolt.api.settings
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import chat.revolt.api.routes.microservices.geo.GeoResponse
|
||||
|
||||
object GeoStateProvider {
|
||||
var geoState by mutableStateOf<GeoResponse?>(null)
|
||||
private set
|
||||
|
||||
fun updateGeoState(newGeoState: GeoResponse?) {
|
||||
checkNotNull(newGeoState) { "You shall not unset this value" }
|
||||
check(if (geoState?.isAgeRestrictedGeo == true) newGeoState.isAgeRestrictedGeo else true) { "You shall not apply a laxer value" }
|
||||
|
||||
geoState = newGeoState
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ fun ChannelIcon(channelType: ChannelType, modifier: Modifier = Modifier) {
|
|||
when (channelType) {
|
||||
ChannelType.TextChannel -> {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.icn_tag_24dp),
|
||||
painter = painterResource(R.drawable.icn_grid_3x3_24dp),
|
||||
contentDescription = stringResource(R.string.channel_text),
|
||||
modifier = modifier
|
||||
)
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ import chat.revolt.api.schemas.ChannelType
|
|||
import chat.revolt.api.schemas.ServerFlags
|
||||
import chat.revolt.api.schemas.User
|
||||
import chat.revolt.api.schemas.has
|
||||
import chat.revolt.api.settings.GeoStateProvider
|
||||
import chat.revolt.api.settings.NotificationSettingsProvider
|
||||
import chat.revolt.api.settings.SyncedSettings
|
||||
import chat.revolt.composables.generic.GroupIcon
|
||||
|
|
@ -914,7 +915,17 @@ fun ChannelItem(
|
|||
.fillMaxWidth()) {
|
||||
when (iconType) {
|
||||
is ChannelItemIconType.Channel -> {
|
||||
ChannelIcon(iconType.type)
|
||||
when {
|
||||
GeoStateProvider.geoState?.isAgeRestrictedGeo == true &&
|
||||
channel.nsfw == true -> {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.icn_grid_3x3_off_24dp),
|
||||
contentDescription = stringResource(R.string.geogate_channel_icon_alt),
|
||||
)
|
||||
}
|
||||
|
||||
else -> ChannelIcon(iconType.type)
|
||||
}
|
||||
}
|
||||
|
||||
is ChannelItemIconType.Painter -> {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
package chat.revolt.composables.vectorassets
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
val GeoGateUX: ImageVector
|
||||
@Composable
|
||||
get() {
|
||||
if (_GeoGateUX != null) {
|
||||
return _GeoGateUX!!
|
||||
}
|
||||
_GeoGateUX = ImageVector.Builder(
|
||||
name = "GeoGate",
|
||||
defaultWidth = 282.dp,
|
||||
defaultHeight = 342.dp,
|
||||
viewportWidth = 282f,
|
||||
viewportHeight = 342f
|
||||
).apply {
|
||||
path(fill = SolidColor(MaterialTheme.colorScheme.onBackground)) {
|
||||
moveTo(173.19f, 30.41f)
|
||||
lineTo(207.56f, 26.67f)
|
||||
lineTo(214.24f, 88.06f)
|
||||
lineTo(171.27f, 92.73f)
|
||||
lineTo(167.48f, 80.72f)
|
||||
lineTo(133.1f, 84.46f)
|
||||
lineTo(136.83f, 118.76f)
|
||||
curveTo(148.6f, 118.88f, 160.25f, 121.25f, 171.13f, 125.76f)
|
||||
curveTo(182.29f, 130.38f, 192.44f, 137.16f, 200.98f, 145.71f)
|
||||
curveTo(209.53f, 154.25f, 216.31f, 164.4f, 220.93f, 175.56f)
|
||||
curveTo(225.56f, 186.73f, 227.94f, 198.69f, 227.94f, 210.78f)
|
||||
curveTo(227.94f, 235.19f, 218.24f, 258.59f, 200.98f, 275.85f)
|
||||
curveTo(183.72f, 293.11f, 160.32f, 302.8f, 135.91f, 302.8f)
|
||||
curveTo(123.83f, 302.8f, 111.86f, 300.42f, 100.69f, 295.8f)
|
||||
curveTo(89.53f, 291.17f, 79.38f, 284.4f, 70.84f, 275.85f)
|
||||
curveTo(53.58f, 258.59f, 43.88f, 235.19f, 43.88f, 210.78f)
|
||||
curveTo(43.88f, 186.37f, 53.58f, 162.96f, 70.84f, 145.71f)
|
||||
curveTo(79.38f, 137.16f, 89.53f, 130.38f, 100.69f, 125.76f)
|
||||
curveTo(108.32f, 122.6f, 116.32f, 120.49f, 124.48f, 119.47f)
|
||||
lineTo(114.15f, 24.41f)
|
||||
lineTo(169.4f, 18.4f)
|
||||
lineTo(173.19f, 30.41f)
|
||||
close()
|
||||
moveTo(64.22f, 194.31f)
|
||||
curveTo(63.03f, 199.55f, 62.29f, 205.07f, 62.29f, 210.78f)
|
||||
curveTo(62.29f, 248.03f, 89.92f, 278.76f, 125.86f, 283.64f)
|
||||
lineTo(126.71f, 265.99f)
|
||||
curveTo(121.83f, 265.99f, 117.14f, 264.05f, 113.69f, 260.6f)
|
||||
curveTo(110.24f, 257.15f, 108.3f, 252.47f, 108.3f, 247.59f)
|
||||
verticalLineTo(238.39f)
|
||||
lineTo(64.22f, 194.31f)
|
||||
close()
|
||||
moveTo(163.52f, 146.36f)
|
||||
curveTo(163.52f, 151.24f, 161.58f, 155.92f, 158.13f, 159.38f)
|
||||
curveTo(154.68f, 162.83f, 149.99f, 164.77f, 145.11f, 164.77f)
|
||||
horizontalLineTo(126.71f)
|
||||
verticalLineTo(183.17f)
|
||||
curveTo(126.71f, 185.61f, 125.74f, 187.95f, 124.01f, 189.68f)
|
||||
curveTo(122.29f, 191.4f, 119.95f, 192.37f, 117.51f, 192.37f)
|
||||
horizontalLineTo(99.1f)
|
||||
verticalLineTo(210.78f)
|
||||
horizontalLineTo(154.32f)
|
||||
curveTo(156.76f, 210.78f, 159.1f, 211.75f, 160.82f, 213.48f)
|
||||
curveTo(162.55f, 215.2f, 163.52f, 217.54f, 163.52f, 219.98f)
|
||||
verticalLineTo(247.59f)
|
||||
horizontalLineTo(172.72f)
|
||||
curveTo(180.91f, 247.59f, 187.81f, 253.02f, 190.21f, 260.38f)
|
||||
curveTo(198.23f, 251.63f, 204.01f, 241.07f, 207.06f, 229.59f)
|
||||
curveTo(210.1f, 218.12f, 210.33f, 206.07f, 207.7f, 194.5f)
|
||||
curveTo(205.08f, 182.92f, 199.69f, 172.15f, 191.99f, 163.11f)
|
||||
curveTo(184.3f, 154.07f, 174.53f, 147.03f, 163.52f, 142.59f)
|
||||
verticalLineTo(146.36f)
|
||||
close()
|
||||
}
|
||||
path(fill = SolidColor(MaterialTheme.colorScheme.error)) {
|
||||
moveTo(281.1f, 314.38f)
|
||||
lineTo(246.16f, 341.74f)
|
||||
lineTo(0.5f, 28.08f)
|
||||
lineTo(35.43f, 0.72f)
|
||||
lineTo(281.1f, 314.38f)
|
||||
close()
|
||||
}
|
||||
}.build()
|
||||
|
||||
return _GeoGateUX!!
|
||||
}
|
||||
|
||||
@Suppress("ObjectPropertyName")
|
||||
private var _GeoGateUX: ImageVector? = null
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,81 @@
|
|||
package chat.revolt.screens.chat.views.channel
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.settings.GeoStateProvider
|
||||
import chat.revolt.composables.vectorassets.GeoGateUX
|
||||
|
||||
@Composable
|
||||
fun ChannelScreenGeoGate(
|
||||
onAcknowledge: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically)
|
||||
) {
|
||||
Image(
|
||||
imageVector = GeoGateUX,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(128.dp),
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.geogate_header),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
textAlign = TextAlign.Center
|
||||
),
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
)
|
||||
|
||||
when (GeoStateProvider.geoState?.countryCode) {
|
||||
"GB" -> {
|
||||
Text(
|
||||
text = stringResource(R.string.geogate_description_variant_osa_uk_25),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
textAlign = TextAlign.Center
|
||||
),
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
Text(
|
||||
text = stringResource(R.string.geogate_description),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
textAlign = TextAlign.Center
|
||||
),
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Button(onClick = { onAcknowledge() }) {
|
||||
Text(stringResource(R.string.geogate_acknowledge))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun GeoGatePreview() {
|
||||
ChannelScreenGeoGate { }
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ import chat.revolt.api.routes.user.addUserIfUnknown
|
|||
import chat.revolt.api.routes.user.fetchUser
|
||||
import chat.revolt.api.schemas.Channel
|
||||
import chat.revolt.api.schemas.Message
|
||||
import chat.revolt.api.settings.GeoStateProvider
|
||||
import chat.revolt.callbacks.Action
|
||||
import chat.revolt.callbacks.ActionChannel
|
||||
import chat.revolt.callbacks.UiCallback
|
||||
|
|
@ -103,6 +104,7 @@ class ChannelScreenViewModel @Inject constructor(
|
|||
var editingMessage by mutableStateOf<String?>(null)
|
||||
|
||||
var ageGateUnlocked by mutableStateOf<Boolean?>(null)
|
||||
var showGeoGate by mutableStateOf(false)
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
|
|
@ -126,6 +128,10 @@ class ChannelScreenViewModel @Inject constructor(
|
|||
this.denyMessageFieldReasonResource = R.string.typing_blank
|
||||
this.editingMessage = null
|
||||
this.ageGateUnlocked = channel?.nsfw != true
|
||||
this.showGeoGate = when {
|
||||
channel?.nsfw == true && GeoStateProvider.geoState?.isAgeRestrictedGeo == true -> true
|
||||
else -> false
|
||||
}
|
||||
viewModelScope.launch {
|
||||
if (ageGateUnlocked != true) {
|
||||
ageGateUnlocked = AgeGateUnlockedStorageProvider.getAgeGateUnlocked()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M320,800L320,640L160,640L160,560L320,560L320,400L160,400L160,320L320,320L320,160L400,160L400,320L560,320L560,160L640,160L640,320L800,320L800,400L640,400L640,560L800,560L800,640L640,640L640,800L560,800L560,640L400,640L400,800L320,800ZM400,560L560,560L560,400L400,400L400,560Z"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M753,640L673,560L800,560L800,640L753,640ZM640,527L433,320L560,320L560,160L640,160L640,320L800,320L800,400L640,400L640,527ZM400,287L320,207L320,160L400,160L400,287ZM791,904L640,753L640,800L560,800L560,673L527,640L400,640L400,800L320,800L320,640L160,640L160,560L320,560L320,433L287,400L160,400L160,320L207,320L56,169L112,112L848,848L791,904Z"/>
|
||||
</vector>
|
||||
|
|
@ -794,4 +794,10 @@
|
|||
<string name="keyboard_shortcut_messaging">Messaging</string>
|
||||
<string name="keyboard_shortcut_messaging_new_line">New Line</string>
|
||||
<string name="keyboard_shortcut_messaging_send_message">Send Message</string>
|
||||
|
||||
<string name="geogate_header">Not available in your region</string>
|
||||
<string name="geogate_channel_icon_alt">Unavailable channel</string>
|
||||
<string name="geogate_description">Revolt may block content in certain jurisdictions in response to legislation or legal notices</string>
|
||||
<string name="geogate_description_variant_osa_uk_25" translatable="false">This channel is not available in your region while we review options on legal compliance.</string>
|
||||
<string name="geogate_acknowledge">Acknowledge</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Reference in New Issue