wip
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+4
@@ -5,8 +5,12 @@
|
|||||||
<set>
|
<set>
|
||||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="StudioBotProjectSettings">
|
||||||
|
<option name="shareContext" value="OptedIn" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -11,6 +11,7 @@ import io.ktor.client.plugins.cache.HttpCache
|
|||||||
import io.ktor.client.plugins.logging.LogLevel
|
import io.ktor.client.plugins.logging.LogLevel
|
||||||
import io.ktor.client.plugins.logging.Logger
|
import io.ktor.client.plugins.logging.Logger
|
||||||
import io.ktor.client.plugins.logging.Logging
|
import io.ktor.client.plugins.logging.Logging
|
||||||
|
import ru.risdeveau.pixeldragon.api.MatrixSyncService
|
||||||
import ru.risdeveau.pixeldragon.api.getMe
|
import ru.risdeveau.pixeldragon.api.getMe
|
||||||
import splitties.preferences.Preferences
|
import splitties.preferences.Preferences
|
||||||
|
|
||||||
@@ -31,20 +32,22 @@ lateinit var homeserver: String
|
|||||||
lateinit var baseUrl: String
|
lateinit var baseUrl: String
|
||||||
lateinit var token: String
|
lateinit var token: String
|
||||||
|
|
||||||
object AccountData : Preferences("settings") {
|
object AccountData : Preferences("system_parameters") {
|
||||||
var token by stringPref("token", "")
|
var token by stringOrNullPref("token", null)
|
||||||
var homeserver by stringPref("homeserver", "")
|
var homeserver by stringOrNullPref("homeserver", null)
|
||||||
|
var syncLastBatch by stringOrNullPref("next_batch", null)
|
||||||
|
var filter by stringOrNullPref("filter", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val syncService = MatrixSyncService()
|
||||||
|
|
||||||
suspend fun initCheck(): Boolean {
|
suspend fun initCheck(): Boolean {
|
||||||
Log.d("initCheck", "checking...")
|
Log.d("initCheck", "checking...")
|
||||||
|
|
||||||
token = AccountData.token
|
token = AccountData.token ?: return false
|
||||||
homeserver = AccountData.homeserver
|
homeserver = AccountData.homeserver ?: return false
|
||||||
|
|
||||||
if (token.isEmpty() or homeserver.isEmpty()) return false
|
|
||||||
|
|
||||||
baseUrl = "$homeserver/_matrix/client/v3"
|
baseUrl = "$homeserver/_matrix/client/v3"
|
||||||
|
|
||||||
return getMe() != null
|
return getMe() != null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,43 +14,45 @@ import org.json.JSONObject
|
|||||||
import ru.risdeveau.pixeldragon.baseUrl
|
import ru.risdeveau.pixeldragon.baseUrl
|
||||||
import ru.risdeveau.pixeldragon.client
|
import ru.risdeveau.pixeldragon.client
|
||||||
import ru.risdeveau.pixeldragon.token
|
import ru.risdeveau.pixeldragon.token
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
class Event (
|
class Event (
|
||||||
val id: String,
|
val id: String,
|
||||||
val rid: String,
|
val rid: String,
|
||||||
val sender: String,
|
|
||||||
val type: String,
|
val type: String,
|
||||||
val content: JSONObject
|
val content: JSONObject,
|
||||||
|
val time: Instant,
|
||||||
|
val sender: String
|
||||||
) {
|
) {
|
||||||
constructor(json: JSONObject) : this(
|
// constructor(json: JSONObject) : this(
|
||||||
json.getString("event_id"),
|
// json.getString("event_id"),
|
||||||
json.getString("room_id"),
|
// json.getString("room_id"),
|
||||||
json.getString("sender"),
|
// json.getString("sender"),
|
||||||
json.getString("type"),
|
// json.getString("type"),
|
||||||
json.getJSONObject("content")
|
// json.getJSONObject("content")
|
||||||
)
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
data class EventsAround (
|
//data class EventsAround (
|
||||||
val base: Event,
|
// val base: Event,
|
||||||
val before: List<Event>,
|
// val before: List<Event>,
|
||||||
val after: List<Event>
|
// val after: List<Event>
|
||||||
)
|
//)
|
||||||
|
//
|
||||||
suspend fun getEventsAround(room: String, event: String): EventsAround {
|
//suspend fun getEventsAround(room: String, event: String): EventsAround {
|
||||||
val r = client.get("$baseUrl/rooms/$room/context/$event") {
|
// val r = client.get("$baseUrl/rooms/$room/context/$event") {
|
||||||
bearerAuth(token)
|
// bearerAuth(token)
|
||||||
parameter("limit", "50")
|
// parameter("limit", "50")
|
||||||
}
|
// }
|
||||||
val json = JSONObject(r.bodyAsText())
|
// val json = JSONObject(r.bodyAsText())
|
||||||
|
//
|
||||||
return EventsAround(
|
// return EventsAround(
|
||||||
Event(json.getJSONObject("event")),
|
// Event(json.getJSONObject("event")),
|
||||||
if (json.has("events_before")) json.getJSONArray("events_before").let {
|
// if (json.has("events_before")) json.getJSONArray("events_before").let {
|
||||||
List<Event>(it.length()) { i -> Event(it.getJSONObject(i))}.reversed()
|
// List<Event>(it.length()) { i -> Event(it.getJSONObject(i))}.reversed()
|
||||||
} else listOf(),
|
// } else listOf(),
|
||||||
if (json.has("events_after")) json.getJSONArray("events_after").let {
|
// if (json.has("events_after")) json.getJSONArray("events_after").let {
|
||||||
List<Event>(it.length()) { i -> Event(it.getJSONObject(i))}
|
// List<Event>(it.length()) { i -> Event(it.getJSONObject(i))}
|
||||||
} else listOf()
|
// } else listOf()
|
||||||
)
|
// )
|
||||||
}
|
//}
|
||||||
+237
@@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
* Created by sweetbread
|
||||||
|
* Copyright (c) 2025. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.risdeveau.pixeldragon.api
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import io.ktor.client.request.bearerAuth
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.client.request.header
|
||||||
|
import io.ktor.client.request.parameter
|
||||||
|
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.HttpStatusCode
|
||||||
|
import io.ktor.http.contentType
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.json.JSONObject
|
||||||
|
import ru.risdeveau.pixeldragon.AccountData.syncLastBatch
|
||||||
|
import ru.risdeveau.pixeldragon.baseUrl
|
||||||
|
import ru.risdeveau.pixeldragon.client
|
||||||
|
import ru.risdeveau.pixeldragon.db.isConnected
|
||||||
|
import ru.risdeveau.pixeldragon.token
|
||||||
|
import ru.risdeveau.pixeldragon.ui.activity.ME
|
||||||
|
import splitties.init.appCtx
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
class MatrixSyncService {
|
||||||
|
private val _syncState = MutableStateFlow<SyncState>(SyncState.Idle)
|
||||||
|
val syncState: StateFlow<SyncState> = _syncState.asStateFlow()
|
||||||
|
|
||||||
|
private var syncJob: Job? = null
|
||||||
|
// private var isInitialized = false
|
||||||
|
//
|
||||||
|
// fun initialize() {
|
||||||
|
// isInitialized = true
|
||||||
|
// }
|
||||||
|
|
||||||
|
fun startSync() {
|
||||||
|
// if (!isInitialized)
|
||||||
|
// throw IllegalStateException("Sync service not initialized")
|
||||||
|
|
||||||
|
if (syncJob?.isActive == true) return
|
||||||
|
|
||||||
|
syncJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
if (syncLastBatch == "") {
|
||||||
|
_syncState.value = SyncState.Syncing
|
||||||
|
try {
|
||||||
|
processSyncResponse(initialSync())
|
||||||
|
_syncState.value = SyncState.Idle
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_syncState.value = SyncState.Error(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// while (isActive) {
|
||||||
|
// try {
|
||||||
|
// val response = sync (
|
||||||
|
// timeout = 30000,
|
||||||
|
// filter = getFilter()
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// processSyncResponse(response)
|
||||||
|
//
|
||||||
|
// } catch (e: Exception) {
|
||||||
|
// Log.w("sync", e.message.toString())
|
||||||
|
// delay(5000) // Wait before retry
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun stopSync() {
|
||||||
|
syncJob?.cancel()
|
||||||
|
_syncState.value = SyncState.Idle
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pauseSync() {
|
||||||
|
// Called when app goes to background
|
||||||
|
syncJob?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resumeSync() {
|
||||||
|
// Called when app comes to foreground
|
||||||
|
if (!isSyncActive()) {
|
||||||
|
startSync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSyncActive(): Boolean = syncJob?.isActive == true
|
||||||
|
|
||||||
|
private suspend fun processSyncResponse(response: JSONObject) {
|
||||||
|
// Log.d("syncResponse", response.toString(2))
|
||||||
|
// val newMessages = mutableListOf<Message>()
|
||||||
|
// val roomUpdates = mutableListOf<RoomUpdate>()
|
||||||
|
//
|
||||||
|
// response.rooms?.join?.forEach { (roomId, roomData) ->
|
||||||
|
// // Process timeline events (messages)
|
||||||
|
// roomData.timeline?.events?.forEach { event ->
|
||||||
|
// val message = event.toMessage(roomId)
|
||||||
|
// database.messageDao().insertMessage(message)
|
||||||
|
// newMessages.add(message)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Process room state updates
|
||||||
|
// roomData.state?.events?.forEach { event ->
|
||||||
|
// when (event.type) {
|
||||||
|
// "m.room.name", "m.room.avatar" -> {
|
||||||
|
// roomUpdates.add(RoomUpdate(roomId, event.type, event.content))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Process ephemeral events (typing, receipts)
|
||||||
|
// roomData.ephemeral?.events?.forEach { event ->
|
||||||
|
// when (event.type) {
|
||||||
|
// "m.typing" -> handleTypingEvent(roomId, event)
|
||||||
|
// "m.receipt" -> handleReceiptEvent(roomId, event)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Notify the app about new data
|
||||||
|
// if (newMessages.isNotEmpty()) {
|
||||||
|
// _newMessages.emit(newMessages)
|
||||||
|
// }
|
||||||
|
// if (roomUpdates.isNotEmpty()) {
|
||||||
|
// _roomUpdates.emit(roomUpdates)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SyncState {
|
||||||
|
object Idle : SyncState()
|
||||||
|
object Syncing : SyncState()
|
||||||
|
object Success : SyncState()
|
||||||
|
data class Error(val message: String?) : SyncState()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun sync(): JSONObject {
|
||||||
|
|
||||||
|
return JSONObject()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun initialSync(): JSONObject {
|
||||||
|
val initialFilter = """
|
||||||
|
{
|
||||||
|
"room": {
|
||||||
|
"state": {
|
||||||
|
"types": [
|
||||||
|
"m.room.name",
|
||||||
|
"m.room.avatar",
|
||||||
|
"m.room.canonical_alias",
|
||||||
|
"m.room.encryption",
|
||||||
|
"m.room.tombstone",
|
||||||
|
"m.room.power_levels",
|
||||||
|
"m.room.member"
|
||||||
|
],
|
||||||
|
"lazy_load_members": true,
|
||||||
|
"not_types": []
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"limit": 10,
|
||||||
|
"types": ["m.room.message"],
|
||||||
|
"not_types": [
|
||||||
|
"m.room.name",
|
||||||
|
"m.room.avatar",
|
||||||
|
"m.room.canonical_alias",
|
||||||
|
"m.room.encryption",
|
||||||
|
"m.room.tombstone",
|
||||||
|
"m.room.power_levels",
|
||||||
|
"m.room.member",
|
||||||
|
"m.call.*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ephemeral": {
|
||||||
|
"types": [],
|
||||||
|
"not_types": ["m.typing", "m.receipt"]
|
||||||
|
},
|
||||||
|
"include_leave": false
|
||||||
|
},
|
||||||
|
"presence": {
|
||||||
|
"types": [],
|
||||||
|
"not_types": ["*"]
|
||||||
|
},
|
||||||
|
"event_format": "client",
|
||||||
|
"event_fields": [
|
||||||
|
"type",
|
||||||
|
"content",
|
||||||
|
"sender",
|
||||||
|
"state_key",
|
||||||
|
"room_id",
|
||||||
|
"origin_server_ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
var r = client.post("$baseUrl/user/${ME!!.userId}/filter") {
|
||||||
|
setBody(initialFilter)
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
bearerAuth(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.status != HttpStatusCode.OK)
|
||||||
|
throw IllegalStateException("Failed to create a filter")
|
||||||
|
val filterId = JSONObject(r.bodyAsText()).getString("filter_id")
|
||||||
|
// val filterId = "vmNk"
|
||||||
|
|
||||||
|
/*val*/ r = client.get("$baseUrl/sync") {
|
||||||
|
bearerAuth(token)
|
||||||
|
url {
|
||||||
|
parameter("filter", filterId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.status != HttpStatusCode.OK)
|
||||||
|
throw IllegalStateException("Failed to sync")
|
||||||
|
Log.d("initialSync", "Response size: ${r.bodyAsText().length}")
|
||||||
|
|
||||||
|
val json = JSONObject(r.bodyAsText())
|
||||||
|
syncLastBatch = json.getString("next_batch")
|
||||||
|
|
||||||
|
return json
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ import kotlinx.coroutines.withContext
|
|||||||
import ru.risdeveau.pixeldragon.api.Me
|
import ru.risdeveau.pixeldragon.api.Me
|
||||||
import ru.risdeveau.pixeldragon.api.getMe
|
import ru.risdeveau.pixeldragon.api.getMe
|
||||||
import ru.risdeveau.pixeldragon.initCheck
|
import ru.risdeveau.pixeldragon.initCheck
|
||||||
|
import ru.risdeveau.pixeldragon.syncService
|
||||||
import ru.risdeveau.pixeldragon.ui.layout.Room
|
import ru.risdeveau.pixeldragon.ui.layout.Room
|
||||||
import ru.risdeveau.pixeldragon.ui.layout.RoomList
|
import ru.risdeveau.pixeldragon.ui.layout.RoomList
|
||||||
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
|
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
|
||||||
@@ -64,6 +65,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
if (initCheck()) {
|
if (initCheck()) {
|
||||||
ME = withContext(Dispatchers.IO) { getMe() }
|
ME = withContext(Dispatchers.IO) { getMe() }
|
||||||
if (ME != null) {
|
if (ME != null) {
|
||||||
|
syncService.startSync()
|
||||||
navController.navigate("rooms")
|
navController.navigate("rooms")
|
||||||
} else {
|
} else {
|
||||||
start<Login>()
|
start<Login>()
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ fun RoomList(modifier: Modifier = Modifier, navController: NavController) {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
// LaunchedEffect(Unit) {
|
||||||
list = withContext(Dispatchers.IO) { Room.getJoined() }
|
// list = withContext(Dispatchers.IO) { Room.getJoined() }
|
||||||
}
|
// }
|
||||||
|
|
||||||
LazyColumn(modifier = modifier, state = listState) {
|
LazyColumn(modifier = modifier, state = listState) {
|
||||||
items(list) { room ->
|
items(list) { room ->
|
||||||
|
|||||||
Reference in New Issue
Block a user