Skip to content

Commit 63b6f89

Browse files
committed
change the way to detect server compatibility
1 parent 0dd564c commit 63b6f89

File tree

8 files changed

+119
-21
lines changed

8 files changed

+119
-21
lines changed

compose-ui/src/commonMain/composeResources/values/strings.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@
444444
<string name="service_select_instance_input_placeholder">Instance URL</string>
445445
<string name="service_select_next_button">Next</string>
446446
<string name="service_select_empty_message">No instances found</string>
447-
447+
<string name="service_select_compatibility_warning">This server uses %1$s, Flare will run in compatibility mode. Some features may not work properly.</string>
448448

449449
<string name="home_tab_home_title">Home</string>
450450
<string name="home_tab_notifications_title">Notifications</string>

compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/screen/login/ServiceSelectionScreenContent.kt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import dev.dimension.flare.compose.ui.bluesky_login_use_password_button
4646
import dev.dimension.flare.compose.ui.bluesky_login_username_hint
4747
import dev.dimension.flare.compose.ui.login_button
4848
import dev.dimension.flare.compose.ui.mastodon_login_verify_message
49+
import dev.dimension.flare.compose.ui.service_select_compatibility_warning
4950
import dev.dimension.flare.compose.ui.service_select_empty_message
5051
import dev.dimension.flare.compose.ui.service_select_instance_input_placeholder
5152
import dev.dimension.flare.compose.ui.service_select_next_button
@@ -162,7 +163,7 @@ public fun ServiceSelectionScreenContent(
162163
state.detectedPlatformType
163164
.onSuccess {
164165
NetworkImage(
165-
it.logoUrl,
166+
it.platformType.logoUrl,
166167
contentDescription = null,
167168
modifier = Modifier.size(24.dp),
168169
contentScale = ContentScale.Fit,
@@ -191,7 +192,18 @@ public fun ServiceSelectionScreenContent(
191192
horizontalAlignment = Alignment.CenterHorizontally,
192193
verticalArrangement = Arrangement.spacedBy(16.dp),
193194
) {
194-
when (state.detectedPlatformType.takeSuccess()) {
195+
state.detectedPlatformType.takeSuccess()?.let {
196+
if (it.compatibleMode) {
197+
PlatformText(
198+
stringResource(
199+
Res.string.service_select_compatibility_warning,
200+
it.software,
201+
),
202+
textAlign = TextAlign.Center,
203+
)
204+
}
205+
}
206+
when (state.detectedPlatformType.takeSuccess()?.platformType) {
195207
null -> Unit
196208
PlatformType.Bluesky -> {
197209
val oauthString =
@@ -516,6 +528,11 @@ public fun ServiceSelectionScreenContent(
516528
PlatformText(
517529
text = stringResource(Res.string.service_select_empty_message),
518530
style = PlatformTheme.typography.title,
531+
textAlign = TextAlign.Center,
532+
modifier =
533+
Modifier
534+
.fillMaxWidth()
535+
.padding(horizontal = screenHorizontalPadding),
519536
)
520537
}
521538
}

shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/InstanceResources.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@ package dev.dimension.flare.data.network.mastodon.api
22

33
import de.jensklingenberg.ktorfit.http.GET
44
import dev.dimension.flare.data.network.mastodon.api.model.InstanceData
5+
import dev.dimension.flare.data.network.mastodon.api.model.InstanceInfoV1
56

67
internal interface InstanceResources {
78
@GET("api/v2/instance")
89
suspend fun instance(): InstanceData
10+
11+
@GET("api/v1/instance")
12+
suspend fun instanceV1(): InstanceInfoV1
913
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package dev.dimension.flare.data.network.mastodon.api.model
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
internal data class InstanceInfoV1(
8+
val uri: String? = null,
9+
val title: String? = null,
10+
@SerialName("short_description")
11+
val shortDescription: String? = null,
12+
val description: String? = null,
13+
val version: String? = null,
14+
)

shared/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/NodeInfoService.kt

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package dev.dimension.flare.data.network.nodeinfo
22

33
import dev.dimension.flare.data.network.bluesky.BlueskyService
44
import dev.dimension.flare.data.network.ktorClient
5+
import dev.dimension.flare.data.network.mastodon.MastodonInstanceService
6+
import dev.dimension.flare.data.network.misskey.MisskeyService
7+
import dev.dimension.flare.data.network.misskey.api.model.MetaRequest
58
import dev.dimension.flare.data.network.nodeinfo.model.NodeInfo
69
import dev.dimension.flare.data.network.nodeinfo.model.Schema10
710
import dev.dimension.flare.data.network.nodeinfo.model.Schema11
@@ -103,24 +106,73 @@ internal data object NodeInfoService {
103106
}.first()
104107
}
105108

106-
suspend fun detectPlatformType(host: String): PlatformType =
109+
suspend fun detectPlatformType(host: String): NodeData =
107110
coroutineScope {
108111
val xqt = listOf(xqtOldHost, "xqt.social", xqtHost)
109112
if (xqt.any { it.equals(host, ignoreCase = true) }) {
110-
return@coroutineScope PlatformType.xQt
113+
return@coroutineScope NodeData(
114+
PlatformType.xQt,
115+
PlatformType.xQt.name,
116+
compatibleMode = false,
117+
)
111118
}
112119
val vvo = listOf(vvoHost, vvo, vvoHostShort, "vvo.social", vvoHostLong)
113120
if (vvo.any { it.equals(host, ignoreCase = true) }) {
114-
return@coroutineScope PlatformType.VVo
121+
return@coroutineScope NodeData(
122+
PlatformType.VVo,
123+
PlatformType.VVo.name,
124+
compatibleMode = false,
125+
)
115126
}
116127
val nodeInfo =
117128
async {
118129
tryRun {
119-
val nodeInfo = fetchNodeInfo(host)
120-
when {
121-
mastodonNodeInfoName.any { it.equals(nodeInfo, ignoreCase = true) } -> PlatformType.Mastodon
122-
misskeyNodeInfoName.any { it.equals(nodeInfo, ignoreCase = true) } -> PlatformType.Misskey
123-
else -> throw IllegalArgumentException("Unsupported platform: $nodeInfo")
130+
val nodeInfo =
131+
fetchNodeInfo(host)
132+
?: throw IllegalArgumentException("NodeInfo not found: $host")
133+
if (mastodonNodeInfoName.any { it.equals(nodeInfo, ignoreCase = true) }) {
134+
NodeData(
135+
platformType = PlatformType.Mastodon,
136+
software = nodeInfo,
137+
compatibleMode =
138+
!nodeInfo.equals(
139+
"mastodon",
140+
ignoreCase = true,
141+
),
142+
)
143+
} else if (misskeyNodeInfoName.any { it.equals(nodeInfo, ignoreCase = true) }) {
144+
NodeData(
145+
platformType = PlatformType.Misskey,
146+
software = nodeInfo,
147+
compatibleMode = !nodeInfo.equals("misskey", ignoreCase = true),
148+
)
149+
} else {
150+
tryRun {
151+
MisskeyService(
152+
"https://$host/api/",
153+
accessTokenFlow = null,
154+
).meta(MetaRequest()).let {
155+
requireNotNull(it.name)
156+
// should be able to use as misskey
157+
NodeData(
158+
platformType = PlatformType.Misskey,
159+
software = nodeInfo,
160+
compatibleMode = true,
161+
)
162+
}
163+
}.getOrElse {
164+
tryRun {
165+
MastodonInstanceService("https://$host/").instance().let {
166+
requireNotNull(it.title)
167+
// should be able to use as mastodon
168+
NodeData(
169+
platformType = PlatformType.Mastodon,
170+
software = nodeInfo,
171+
compatibleMode = true,
172+
)
173+
}
174+
}.getOrNull()
175+
}
124176
}
125177
}.getOrNull()
126178
}
@@ -129,7 +181,11 @@ internal data object NodeInfoService {
129181
async {
130182
tryRun {
131183
BlueskyService("https://$host").describeServer().requireResponse()
132-
PlatformType.Bluesky
184+
NodeData(
185+
platformType = PlatformType.Bluesky,
186+
software = PlatformType.Bluesky.name,
187+
compatibleMode = false,
188+
)
133189
}.getOrNull()
134190
}
135191

@@ -140,3 +196,10 @@ internal data object NodeInfoService {
140196
?: throw IllegalArgumentException("Unsupported platform: $host")
141197
}
142198
}
199+
200+
public data class NodeData(
201+
val platformType: PlatformType,
202+
val software: String,
203+
// not officially supported, but works fine for basic features
204+
val compatibleMode: Boolean,
205+
)

shared/src/commonMain/kotlin/dev/dimension/flare/data/network/xqt/elonmusk114514/ElonMusk1145141919810.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import io.ktor.client.request.get
88
import io.ktor.client.statement.bodyAsText
99
import io.ktor.util.decodeBase64Bytes
1010
import io.ktor.util.encodeBase64
11+
import kotlinx.serialization.Serializable
1112
import kotlin.experimental.xor
1213
import kotlin.random.Random
1314
import kotlin.time.Clock
14-
import kotlinx.serialization.Serializable
1515

1616
internal object ElonMusk1145141919810 {
1717
@Serializable
@@ -22,13 +22,12 @@ internal object ElonMusk1145141919810 {
2222

2323
private var jsonPair: List<JsonPair>? = null
2424

25-
suspend fun encodeSha256(data: String): ByteArray {
26-
return CryptographyProvider
25+
suspend fun encodeSha256(data: String): ByteArray =
26+
CryptographyProvider
2727
.Default
2828
.get(SHA256)
2929
.hasher()
3030
.hash(data.encodeToByteArray())
31-
}
3231

3332
fun encodeBase64(data: ByteArray): String = data.encodeBase64()
3433

shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/NodeInfoPresenter.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ import dev.dimension.flare.common.PagingState
1717
import dev.dimension.flare.common.toPagingState
1818
import dev.dimension.flare.data.datasource.microblog.RecommendInstancePagingSource
1919
import dev.dimension.flare.data.datasource.microblog.pagingConfig
20+
import dev.dimension.flare.data.network.nodeinfo.NodeData
2021
import dev.dimension.flare.data.network.nodeinfo.NodeInfoService
21-
import dev.dimension.flare.model.PlatformType
2222
import dev.dimension.flare.ui.model.UiInstance
2323
import dev.dimension.flare.ui.model.UiState
24+
import dev.dimension.flare.ui.model.isSuccess
2425
import dev.dimension.flare.ui.presenter.PresenterBase
2526
import kotlinx.coroutines.ExperimentalCoroutinesApi
2627
import kotlinx.coroutines.FlowPreview
@@ -59,7 +60,7 @@ public class NodeInfoPresenter : PresenterBase<NodeInfoState>() {
5960

6061
val detectedPlatformType by remember(filterFlow) {
6162
filterFlow.flatMapLatest {
62-
flow<UiState<PlatformType>> {
63+
flow {
6364
runCatching {
6465
emit(UiState.Loading())
6566
NodeInfoService.detectPlatformType(it)
@@ -75,7 +76,7 @@ public class NodeInfoPresenter : PresenterBase<NodeInfoState>() {
7576
return object : NodeInfoState {
7677
override val instances = instances.toPagingState()
7778
override val detectedPlatformType = detectedPlatformType
78-
override val canNext = detectedPlatformType is UiState.Success<PlatformType>
79+
override val canNext = detectedPlatformType.isSuccess
7980

8081
override fun setFilter(value: String) {
8182
if (filter != value) {
@@ -89,7 +90,7 @@ public class NodeInfoPresenter : PresenterBase<NodeInfoState>() {
8990
@Immutable
9091
public interface NodeInfoState {
9192
public val instances: PagingState<UiInstance>
92-
public val detectedPlatformType: UiState<PlatformType>
93+
public val detectedPlatformType: UiState<NodeData>
9394
public val canNext: Boolean
9495

9596
public fun setFilter(value: String)

shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/GuestConfigPresenter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public class GuestConfigPresenter :
7878
emit(UiState.Loading())
7979
NodeInfoService.detectPlatformType(it)
8080
}.onSuccess {
81-
emit(UiState.Success(it))
81+
emit(UiState.Success(it.platformType))
8282
}.onFailure {
8383
emit(UiState.Error(it))
8484
}

0 commit comments

Comments
 (0)