guilds, lifecycle gateway disconnector, base channels
This commit is contained in:
parent
fcdd237809
commit
0781606a00
14 changed files with 292 additions and 6 deletions
|
|
@ -20,7 +20,7 @@ kotlin {
|
|||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(project(":api:shared"))
|
||||
api(project(":api:shared"))
|
||||
|
||||
implementation(libs.kermit)
|
||||
implementation(libs.ktor.client.core)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package moe.lava.neon.api.gateway
|
|||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import moe.lava.neon.api.ApiConstants
|
||||
import moe.lava.neon.api.gateway.content.GuildSerializer
|
||||
import moe.lava.neon.api.objects.Guild
|
||||
import moe.lava.neon.api.objects.User
|
||||
|
||||
sealed interface Payload {
|
||||
|
|
@ -58,7 +60,8 @@ sealed interface Event {
|
|||
NO_AFFINE_USER_IDS,
|
||||
DEDUPE_USER_OBJECTS,
|
||||
USER_SETTINGS_PROTO,
|
||||
DEBOUNCE_MESSAGE_REACTIONS
|
||||
DEBOUNCE_MESSAGE_REACTIONS,
|
||||
CLIENT_STATE_V2,
|
||||
) },
|
||||
// TODO: Client state v2
|
||||
// val clientState: ClientState,
|
||||
|
|
@ -107,7 +110,7 @@ sealed interface Event {
|
|||
data class Ready(
|
||||
val v: Int,
|
||||
val user: User,
|
||||
// val guilds: List<UnavailableGuild>,
|
||||
val guilds: List<@Serializable(GuildSerializer::class) Guild>,
|
||||
val sessionId: String,
|
||||
val resumeGatewayUrl: String,
|
||||
// val application: Application,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package moe.lava.neon.api.gateway.content
|
||||
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.booleanOrNull
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import moe.lava.neon.api.objects.Guild
|
||||
|
||||
object GuildSerializer : JsonContentPolymorphicSerializer<Guild>(Guild::class) {
|
||||
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Guild> {
|
||||
return if (element.jsonObject["unavailable"]?.jsonPrimitive?.booleanOrNull == true) {
|
||||
Guild.Unavailable.serializer()
|
||||
} else {
|
||||
Guild.Gateway.serializer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ kotlin {
|
|||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(project(":api:shared"))
|
||||
api(project(":api:shared"))
|
||||
implementation(project(":common"))
|
||||
|
||||
implementation(libs.kermit)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ object ApiConstants {
|
|||
namingStrategy = JsonNamingStrategy.SnakeCase
|
||||
ignoreUnknownKeys = true
|
||||
encodeDefaults = true
|
||||
// TODO: Distinguish missing vs null fields
|
||||
explicitNulls = false
|
||||
}
|
||||
|
||||
val superProps = Base64.encode(json.encodeToString(SuperProperties()).encodeToByteArray())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package moe.lava.neon.api.objects
|
||||
|
||||
data class Channel(
|
||||
val id: Snowflake,
|
||||
)
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
package moe.lava.neon.api.objects
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Instant
|
||||
|
||||
@Serializable
|
||||
sealed class Guild {
|
||||
abstract val id: Snowflake
|
||||
abstract val unavailable: Boolean?
|
||||
|
||||
@Serializable
|
||||
data class Unavailable(
|
||||
override val id: Snowflake,
|
||||
override val unavailable: Boolean?,
|
||||
|
||||
val geoRestricted: Boolean?,
|
||||
val name: String?,
|
||||
val icon: String?,
|
||||
) : Guild()
|
||||
|
||||
@Serializable
|
||||
data class Available(
|
||||
override val id: Snowflake,
|
||||
override val unavailable: Boolean?,
|
||||
|
||||
val name: String,
|
||||
val icon: String?,
|
||||
val banner: String?,
|
||||
val homeHeader: String?,
|
||||
val splash: String?,
|
||||
val discoverySplash: String?,
|
||||
val ownerId: Snowflake,
|
||||
val applicationId: Snowflake?,
|
||||
val description: String?,
|
||||
val region: String?,
|
||||
val afkChannelId: Snowflake?,
|
||||
val afkTimeout: Int,
|
||||
val widgetEnabled: Boolean?,
|
||||
val widgetChannelId: Snowflake?,
|
||||
val verificationLevel: Int,
|
||||
val defaultMessageNotifications: Int,
|
||||
val explicitContentFilter: Int,
|
||||
val features: List<String>,
|
||||
// val stickers: List<Sticker> = listOf(),
|
||||
// val roles: List<Role> = listOf(),
|
||||
// val emojis: List<Emoji> = listOf(),
|
||||
|
||||
val mfaLevel: Int,
|
||||
val systemChannelId: Snowflake?,
|
||||
val systemChannelFlags: Int,
|
||||
val rulesChannelId: Snowflake?,
|
||||
val publicUpdatesChannelId: Snowflake?,
|
||||
val safetyAlertsChannelId: Snowflake?,
|
||||
val maxPresences: Int?,
|
||||
val maxMembers: Int?,
|
||||
val vanityUrlCode: String?,
|
||||
val premiumTier: Int,
|
||||
val premiumSubscriptionCount: Int?,
|
||||
val preferredLocale: String,
|
||||
val maxVideoChannelUsers: Int?,
|
||||
val maxStageVideoChannelUsers: Int?,
|
||||
val nsfwLevel: Int,
|
||||
val ownerConfiguredContentLevel: Int?,
|
||||
val hubType: Int?,
|
||||
val premiumProgressBarEnabled: Boolean,
|
||||
val latestOnboardingQuestionId: Snowflake?,
|
||||
// val incidents_data: AutomodIncidentsData?,
|
||||
// val premium_features: GuildPremiumFeatures?,
|
||||
// val profile: GuildIdentity?,
|
||||
val approximateMemberCount: Int?,
|
||||
val approximatePresenceCount: Int?,
|
||||
): Guild()
|
||||
|
||||
@Serializable
|
||||
data class Gateway(
|
||||
override val id: Snowflake,
|
||||
override val unavailable: Boolean?,
|
||||
|
||||
val joinedAt: Instant,
|
||||
val large: Boolean,
|
||||
val geoRestricted: Boolean?,
|
||||
val memberCount: Int,
|
||||
// val members: List<GuildMember>,
|
||||
// val channels: List<Channel>,
|
||||
// val threads: List<Channel>,
|
||||
// val presences: List<Presence>,
|
||||
// val voiceStates: List<VoiceState>,
|
||||
// val activityInstances: List<EmbeddedActivityInstance>,
|
||||
// val stageInstances: List<StageInstance>,
|
||||
// val guildScheduledEvents: List<GuildScheduledEvent>,
|
||||
val dataMode: String,
|
||||
val properties: Available, // Client state v2, this is the below fields
|
||||
// val stickers: List<Sticker>,
|
||||
// val roles: List<Role>,
|
||||
// val emojis: List<Emoji>,
|
||||
// val soundboardSounds: List<SoundboardSound>,
|
||||
// override val premiumSubscriptionCount: Int,
|
||||
//
|
||||
// override val id: Snowflake,
|
||||
// override val unavailable: Boolean?,
|
||||
//
|
||||
// override val name: String,
|
||||
// override val icon: String?,
|
||||
// override val banner: String?,
|
||||
// override val homeHeader: String?,
|
||||
// override val splash: String?,
|
||||
// override val discoverySplash: String?,
|
||||
// override val ownerId: Snowflake,
|
||||
// override val applicationId: Snowflake?,
|
||||
// override val description: String?,
|
||||
// override val region: String?,
|
||||
// override val afkChannelId: Snowflake?,
|
||||
// override val afkTimeout: Int,
|
||||
// override val widgetEnabled: Boolean?,
|
||||
// override val widgetChannelId: Snowflake?,
|
||||
// override val verificationLevel: Int,
|
||||
// override val defaultMessageNotifications: Int,
|
||||
// override val explicitContentFilter: Int,
|
||||
// override val features: List<String>,
|
||||
//
|
||||
// override val mfaLevel: Int,
|
||||
// override val systemChannelId: Snowflake?,
|
||||
// override val systemChannelFlags: Int,
|
||||
// override val rulesChannelId: Snowflake?,
|
||||
// override val publicUpdatesChannelId: Snowflake?,
|
||||
// override val safetyAlertsChannelId: Snowflake?,
|
||||
// override val maxPresences: Int?,
|
||||
// override val maxMembers: Int?,
|
||||
// override val vanityUrlCode: String?,
|
||||
// override val premiumTier: Int,
|
||||
// override val preferredLocale: String,
|
||||
// override val maxVideoChannelUsers: Int?,
|
||||
// override val maxStageVideoChannelUsers: Int?,
|
||||
// override val nsfwLevel: Int,
|
||||
// override val ownerConfiguredContentLevel: Int?,
|
||||
// override val hubType: Int?,
|
||||
// override val premiumProgressBarEnabled: Boolean,
|
||||
// override val latestOnboardingQuestionId: Snowflake?,
|
||||
// // override val incidents_data: AutomodIncidentsData?
|
||||
// // override val premium_features: GuildPremiumFeatures?
|
||||
// // override val profile: GuildIdentity?
|
||||
// override val approximateMemberCount: Int?,
|
||||
// override val approximatePresenceCount: Int?,
|
||||
) : Guild()
|
||||
}
|
||||
|
|
@ -26,6 +26,9 @@ kotlin {
|
|||
implementation(project(":api:rest"))
|
||||
implementation(project(":common"))
|
||||
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.kotlinx.serialization.core)
|
||||
|
||||
implementation(project.dependencies.platform(libs.koin.bom))
|
||||
implementation(libs.koin.core)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package moe.lava.neon.core.data.guild
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.flatMapConcat
|
||||
import kotlinx.coroutines.flow.map
|
||||
import moe.lava.neon.api.gateway.Event
|
||||
import moe.lava.neon.api.gateway.GatewayHandler
|
||||
import moe.lava.neon.core.model.Guild
|
||||
import moe.lava.neon.api.objects.Guild as ApiGuild
|
||||
|
||||
class GuildGatewayDataSource(
|
||||
gateway: GatewayHandler,
|
||||
) {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val flow = gateway.events
|
||||
.filterIsInstance<Event.Ready>()
|
||||
.flatMapConcat { it.guilds.asFlow() }
|
||||
.filterIsInstance<ApiGuild.Gateway>()
|
||||
.map { apiGuild ->
|
||||
Guild(
|
||||
id = apiGuild.id,
|
||||
name = apiGuild.properties.name,
|
||||
iconHash = apiGuild.properties.icon,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,21 +3,27 @@ package moe.lava.neon.core.di
|
|||
import moe.lava.neon.api.ApiClient
|
||||
import moe.lava.neon.api.gateway.GatewayHandler
|
||||
import moe.lava.neon.core.AppSettings
|
||||
import moe.lava.neon.core.data.guild.GuildGatewayDataSource
|
||||
import moe.lava.neon.core.repository.AuthRepository
|
||||
import moe.lava.neon.core.repository.CaptchaRepository
|
||||
import moe.lava.neon.core.repository.GatewayRepository
|
||||
import moe.lava.neon.core.repository.GuildRepository
|
||||
import moe.lava.neon.core.repository.UserRepository
|
||||
import org.koin.core.module.dsl.createdAtStart
|
||||
import org.koin.core.module.dsl.withOptions
|
||||
import org.koin.dsl.module
|
||||
import org.koin.plugin.module.dsl.single
|
||||
|
||||
val coreModule = module {
|
||||
factory { ApiClient() }
|
||||
single<AppSettings>()
|
||||
single<GatewayHandler>()
|
||||
|
||||
single<GuildGatewayDataSource>()
|
||||
|
||||
single<AuthRepository>()
|
||||
single<CaptchaRepository>()
|
||||
single<GatewayRepository>()
|
||||
single<GuildRepository>() withOptions { createdAtStart() }
|
||||
single<UserRepository>()
|
||||
|
||||
single<GatewayHandler>()
|
||||
}
|
||||
|
|
|
|||
16
core/src/commonMain/kotlin/moe/lava/neon/core/model/Guild.kt
Normal file
16
core/src/commonMain/kotlin/moe/lava/neon/core/model/Guild.kt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package moe.lava.neon.core.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.lava.neon.api.objects.Snowflake
|
||||
|
||||
@Serializable
|
||||
data class Guild(
|
||||
val id: Snowflake,
|
||||
val name: String,
|
||||
val iconHash: String?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UnavailableGuild(val e: String)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package moe.lava.neon.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import moe.lava.neon.api.objects.Snowflake
|
||||
import moe.lava.neon.core.data.guild.GuildGatewayDataSource
|
||||
import moe.lava.neon.core.model.Guild
|
||||
|
||||
class GuildRepository(
|
||||
gatewaySource: GuildGatewayDataSource,
|
||||
) {
|
||||
init {
|
||||
gatewaySource.flow
|
||||
.onEach { guild ->
|
||||
cache[guild.id] = guild
|
||||
joined[guild.id] = guild
|
||||
}
|
||||
.launchIn(CoroutineScope(Dispatchers.IO))
|
||||
}
|
||||
|
||||
private val cache = mutableMapOf<Snowflake, Guild>()
|
||||
private val joined = mutableMapOf<Snowflake, Guild>()
|
||||
|
||||
fun get(id: Snowflake) = cache[id]
|
||||
fun getJoined() = joined.toMap()
|
||||
}
|
||||
|
|
@ -70,6 +70,7 @@ kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest"
|
|||
kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
|
||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" }
|
||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
|
||||
|
|
|
|||
|
|
@ -11,13 +11,17 @@ import androidx.compose.material3.Button
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -34,6 +38,32 @@ fun Sample(
|
|||
onRequestLogout: () -> Unit,
|
||||
) {
|
||||
val viewModel: SampleViewModel = koinViewModel()
|
||||
|
||||
val li = LocalLifecycleOwner.current
|
||||
DisposableEffect(li) {
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_PAUSE -> {
|
||||
viewModel.disconnect()
|
||||
}
|
||||
|
||||
Lifecycle.Event.ON_RESUME -> {
|
||||
viewModel.connect()
|
||||
}
|
||||
|
||||
Lifecycle.Event.ON_DESTROY -> {
|
||||
viewModel.disconnect()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
li.lifecycle.addObserver(observer)
|
||||
onDispose {
|
||||
li.lifecycle.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
|
||||
var showContent by remember { mutableStateOf(false) }
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue