Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83af44002c | |||
| 44acbfa6b7 | |||
| 2d25cb36bd | |||
| 439cf60b66 | |||
| f1f603525b | |||
| 5c7027e559 | |||
| 4bcb2518b7 | |||
| 25852e08a9 |
31
.github/workflows/android.yml
vendored
31
.github/workflows/android.yml
vendored
@@ -1,25 +1,14 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build_type:
|
||||
description: 'Select build type'
|
||||
required: true
|
||||
default: 'release'
|
||||
type: choice
|
||||
options:
|
||||
- release
|
||||
- alpha
|
||||
- beta
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BUILD_TYPE: ${{ github.event.inputs.build_type || 'release' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -34,13 +23,23 @@ jobs:
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Decode Keystore
|
||||
run: |
|
||||
# Decodes the Base64 string from secrets to a physical file
|
||||
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > composeApp/release.jks
|
||||
|
||||
- name: Build APK
|
||||
run: ./gradlew :composeApp:assemble${{ env.BUILD_TYPE }}
|
||||
run: ./gradlew :composeApp:assembleRelease
|
||||
env:
|
||||
KEYSTORE_PATH: release.jks
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Gitea Release
|
||||
uses: akkuman/gitea-release-action@v1
|
||||
with:
|
||||
files: "composeApp/build/outputs/apk/${{ env.BUILD_TYPE }}/*.apk"
|
||||
files: "composeApp/build/outputs/apk/release/*.apk"
|
||||
server_url: "https://git.reya.su/"
|
||||
repository: "reya/coop-mobile"
|
||||
token: ${{ secrets.GITEA_TOKEN }}
|
||||
@@ -1,5 +1,4 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
@@ -20,15 +19,12 @@ kotlin {
|
||||
androidMain.dependencies {
|
||||
implementation(libs.compose.uiToolingPreview)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation("androidx.navigation:navigation-compose:2.8.8")
|
||||
implementation("androidx.datastore:datastore-preferences:1.2.1")
|
||||
implementation("androidx.datastore:datastore-preferences-core:1.2.1")
|
||||
implementation("org.jetbrains.compose.material3:material3:1.11.0-alpha07")
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.androidx.lifecycle.process)
|
||||
implementation("io.coil-kt.coil3:coil-compose:3.4.0")
|
||||
implementation("io.coil-kt.coil3:coil-network-okhttp:3.4.0")
|
||||
implementation("su.reya:nostr-sdk-kmp:0.2.3")
|
||||
implementation("io.github.kalinjul.easyqrscan:scanner:0.7.0")
|
||||
implementation("androidx.lifecycle:lifecycle-process:2.8.0")
|
||||
implementation("io.github.alexzhirkevich:qrose:1.1.2")
|
||||
}
|
||||
commonMain.dependencies {
|
||||
@@ -40,6 +36,8 @@ kotlin {
|
||||
implementation(libs.compose.uiToolingPreview)
|
||||
implementation(libs.androidx.lifecycle.viewmodelCompose)
|
||||
implementation(libs.androidx.lifecycle.runtimeCompose)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.androidx.datastore)
|
||||
implementation(projects.shared)
|
||||
}
|
||||
commonTest.dependencies {
|
||||
@@ -48,23 +46,19 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
val localProperties = Properties().apply {
|
||||
val file = rootProject.file("local.properties")
|
||||
if (file.exists()) {
|
||||
load(file.inputStream())
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "su.reya.coop"
|
||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||
|
||||
base.archivesName.set("coop")
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
storeFile = localProperties.getProperty("keystore.path")?.let { file(it) }
|
||||
storePassword = localProperties.getProperty("keystore.password")
|
||||
keyAlias = localProperties.getProperty("key.alias")
|
||||
keyPassword = localProperties.getProperty("key.password")
|
||||
val path = System.getenv("KEYSTORE_PATH")
|
||||
storeFile = path?.let { file(it) }
|
||||
storePassword = System.getenv("KEYSTORE_PASSWORD")
|
||||
keyAlias = System.getenv("KEY_ALIAS")
|
||||
keyPassword = System.getenv("KEY_PASSWORD")
|
||||
}
|
||||
}
|
||||
defaultConfig {
|
||||
@@ -72,7 +66,7 @@ android {
|
||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
||||
versionCode = 1
|
||||
versionName = "0.1.0"
|
||||
versionName = "0.1.1"
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
@@ -84,24 +78,11 @@ android {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt")
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
}
|
||||
create("beta") {
|
||||
initWith(getByName("release"))
|
||||
applicationIdSuffix = ".beta"
|
||||
versionNameSuffix = "-beta"
|
||||
manifestPlaceholders["appName"] = "Coop Beta"
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
}
|
||||
create("alpha") {
|
||||
initWith(getByName("release"))
|
||||
applicationIdSuffix = ".alpha"
|
||||
versionNameSuffix = "-alpha"
|
||||
manifestPlaceholders["appName"] = "Coop Alpha"
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
|
||||
11
composeApp/proguard-rules.pro
vendored
Normal file
11
composeApp/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
-dontwarn com.sun.jna.**
|
||||
|
||||
-keep class com.sun.jna.** { *; }
|
||||
-keep class * extends com.sun.jna.Structure { *; }
|
||||
-keep class * extends com.sun.jna.Library { *; }
|
||||
-keep class * extends com.sun.jna.Callback { *; }
|
||||
|
||||
-keep class rust.nostr.sdk.** { *; }
|
||||
-keep class su.reya.nostr.** { *; }
|
||||
|
||||
-keepattributes Signature, InnerClasses, EnclosingMethod, RuntimeVisibleAnnotations
|
||||
@@ -15,8 +15,10 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialExpressiveTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.MotionScheme
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
@@ -100,6 +102,8 @@ fun App() {
|
||||
|
||||
MaterialExpressiveTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography(),
|
||||
motionScheme = MotionScheme.expressive(),
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalNostrViewModel provides viewModel,
|
||||
|
||||
@@ -68,8 +68,19 @@ fun ChatScreen(
|
||||
val snackbarHostState = LocalSnackbarHostState.current
|
||||
val viewModel = LocalNostrViewModel.current
|
||||
|
||||
val room = viewModel.getChatRoom(id)
|
||||
val listState = rememberLazyListState()
|
||||
val chatRooms by viewModel.chatRooms.collectAsState()
|
||||
val room = remember(chatRooms, id) { chatRooms.firstOrNull { it.id == id } }
|
||||
|
||||
if (room == null) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
LoadingIndicator()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val displayName by remember(room) { room.displayNameFlow(viewModel) }.collectAsState("Loading...")
|
||||
val picture by remember(room) { room.pictureFlow(viewModel) }.collectAsState(null)
|
||||
|
||||
@@ -58,7 +58,6 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coop.composeapp.generated.resources.Res
|
||||
@@ -85,7 +84,6 @@ fun HomeScreen(
|
||||
onOpenChat: (Long) -> Unit,
|
||||
onNewChat: () -> Unit,
|
||||
) {
|
||||
val clipboard = LocalClipboard.current
|
||||
val navController = LocalNavController.current
|
||||
val snackbarHostState = LocalSnackbarHostState.current
|
||||
val viewModel = LocalNostrViewModel.current
|
||||
@@ -112,15 +110,21 @@ fun HomeScreen(
|
||||
?: remember { mutableStateOf(null) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (qrResult == null) {
|
||||
viewModel.getChatRooms()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(qrResult) {
|
||||
qrResult?.let { result ->
|
||||
runCatching { PublicKey.parse(result) }
|
||||
.onSuccess { pubkey ->
|
||||
try {
|
||||
val roomId = viewModel.createChatRoom(listOf(pubkey))
|
||||
navController.navigate(Screen.Chat(roomId))
|
||||
} catch (e: Exception) {
|
||||
e.message?.let { snackbarHostState.showSnackbar(it) }
|
||||
}
|
||||
}
|
||||
.onFailure { e -> println("Failed to parse QR: ${e.message}") }
|
||||
|
||||
|
||||
@@ -76,7 +76,6 @@ fun ScanScreen(
|
||||
ScannerWithPermissions(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
onScanned = {
|
||||
println("Scanned: $it");
|
||||
onResult(it)
|
||||
true
|
||||
},
|
||||
|
||||
@@ -8,16 +8,19 @@ androidx-appcompat = "1.7.1"
|
||||
androidx-core = "1.18.0"
|
||||
androidx-espresso = "3.7.0"
|
||||
androidx-lifecycle = "2.10.0"
|
||||
androidx-navigation = "2.8.8"
|
||||
androidx-navigation = "2.9.8"
|
||||
androidx-testExt = "1.3.0"
|
||||
composeMultiplatform = "1.10.3"
|
||||
composeMultiplatform = "1.11.0"
|
||||
datastorePreferences = "1.2.1"
|
||||
junit = "4.13.2"
|
||||
kotlin = "2.3.20"
|
||||
kotlinx-serialization = "1.8.0"
|
||||
material3 = "1.10.0-alpha05"
|
||||
ktor = "3.4.3"
|
||||
kotlin = "2.3.21"
|
||||
kotlinx-serialization = "1.11.0"
|
||||
material3 = "1.11.0-alpha07"
|
||||
ktor = "3.5.0"
|
||||
|
||||
[libraries]
|
||||
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
||||
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastorePreferences" }
|
||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||
kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||
junit = { module = "junit:junit", version.ref = "junit" }
|
||||
@@ -31,6 +34,7 @@ kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serializa
|
||||
compose-uiTooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "composeMultiplatform" }
|
||||
androidx-lifecycle-viewmodelCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
|
||||
androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
|
||||
androidx-lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "androidx-lifecycle" }
|
||||
compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeMultiplatform" }
|
||||
compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "composeMultiplatform" }
|
||||
compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "material3" }
|
||||
|
||||
@@ -25,15 +25,15 @@ kotlin {
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation("org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.8.0")
|
||||
implementation("su.reya:nostr-sdk-kmp:0.2.3")
|
||||
implementation("com.squareup.okio:okio:3.16.2")
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.websockets)
|
||||
implementation(libs.ktor.client.content.negotiation)
|
||||
implementation(libs.ktor.serialization.kotlinx.json)
|
||||
implementation(libs.androidx.lifecycle.viewmodelCompose)
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.8.0")
|
||||
implementation("su.reya:nostr-sdk-kmp:0.2.3")
|
||||
implementation("com.squareup.okio:okio:3.16.2")
|
||||
}
|
||||
androidMain.dependencies {
|
||||
implementation(libs.ktor.client.okhttp)
|
||||
|
||||
@@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
@@ -406,6 +407,7 @@ class NostrViewModel(
|
||||
}
|
||||
|
||||
fun createChatRoom(to: List<PublicKey>): Long {
|
||||
try {
|
||||
if (nostr.signer.currentUser == null) throw IllegalStateException("User not signed in")
|
||||
if (to.isEmpty()) throw IllegalArgumentException("At least one recipient is required")
|
||||
|
||||
@@ -417,9 +419,14 @@ class NostrViewModel(
|
||||
|
||||
// Create a room from the rumor event
|
||||
val room = Room.new(rumor, nostr.signer.currentUser!!)
|
||||
_chatRooms.value += room
|
||||
_chatRooms.update { currentRooms ->
|
||||
currentRooms + room
|
||||
}
|
||||
|
||||
return room.id
|
||||
} catch (e: Exception) {
|
||||
throw IllegalArgumentException("Failed to create room: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun getChatRoom(id: Long): Room {
|
||||
@@ -429,10 +436,12 @@ class NostrViewModel(
|
||||
|
||||
fun getChatRooms() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
_chatRooms.value = nostr.getChatRooms() ?: emptySet()
|
||||
} catch (e: Exception) {
|
||||
showError("Error: ${e.message}")
|
||||
val rooms = nostr.getChatRooms() ?: emptySet()
|
||||
_chatRooms.update { currentRooms ->
|
||||
val virtualRooms = currentRooms.filter { local ->
|
||||
rooms.none { db -> db.id == local.id }
|
||||
}
|
||||
rooms + virtualRooms
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user