ContextualMenu

This commit is contained in:
dotmg 2025-07-08 11:58:49 +02:00
parent 82f0624095
commit e261be48f0
19 changed files with 159 additions and 125 deletions

View file

@ -29,6 +29,7 @@ kotlin {
implementation(compose.preview) implementation(compose.preview)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.koin.android) // Koin Android-specific extensions implementation(libs.koin.android) // Koin Android-specific extensions
implementation(libs.koin.androidx.compose)
} }
commonMain.dependencies { commonMain.dependencies {
@ -48,6 +49,11 @@ kotlin {
implementation(libs.serialization) implementation(libs.serialization)
api(libs.kmp.observableviewmodel.core) api(libs.kmp.observableviewmodel.core)
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
implementation(libs.androidx.material3)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)
implementation(libs.koin.compose.viewmodel.navigation)
} }
commonTest.dependencies { commonTest.dependencies {
implementation(libs.kotlin.test) implementation(libs.kotlin.test)
@ -56,7 +62,6 @@ kotlin {
implementation(compose.desktop.currentOs) implementation(compose.desktop.currentOs)
implementation(libs.kotlinx.coroutinesSwing) implementation(libs.kotlinx.coroutinesSwing)
} }
//androidMain.dependsOn(androidAndJvmMain)
} }
} }

View file

@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application <application
android:name=".AndroidApp"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"

View file

@ -8,7 +8,7 @@ import org.koin.android.ext.koin.androidLogger
import org.koin.core.logger.Level import org.koin.core.logger.Level
import mg.dot.feufaro.di.androidModule import mg.dot.feufaro.di.androidModule
class AndroidApp: Application { class AndroidApp: Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
startKoin { startKoin {

View file

@ -1,46 +0,0 @@
package mg.dot.feufaro
import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.File
import java.io.IOException
import java.io.InputStreamReader
// This is just a regular class that implements the common 'FileRepository' interface.
// It is NOT an 'actual' declaration of 'FileRepository'.
class AndroidFileRepository(private val context: Context) : FileRepository { // IMPORTS AND IMPLEMENTS THE commonMain 'FileRepository' interface
override suspend fun readFileLines(filePath: String): List<String> = withContext(Dispatchers.IO) {
try {
when {
filePath.startsWith("assets://") -> {
readAssetFileLines(filePath)
}
else -> {
File(filePath).readLines()
}
}
} catch (e: IOException) {
throw IOException("Failed to read file or asset '$filePath'")
}
}
override suspend fun readFileContent(filePath: String): String = withContext(Dispatchers.IO) { "" }
private fun readAssetFileLines(assetFileName: String): List<String> {
return context.assets.open(assetFileName).use { inputStream ->
BufferedReader(InputStreamReader(inputStream)).useLines { it.toList() }
}
}
private fun readAssetFileContent(assetFileName: String): String {
return context.assets.open(assetFileName).use { inputStream ->
// BufferedReader(InputStreamReader(inputStream)) permet de lire le texte ligne par ligne.
BufferedReader(InputStreamReader(inputStream)).use { reader ->
reader.readText() // Lit tout le contenu en une seule chaîne.
}
}
}
}

View file

@ -6,6 +6,7 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import org.koin.androidx.compose.koinViewModel
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -13,7 +14,8 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
App() val sharedViewModel: SharedViewModel = koinViewModel()
App(sharedViewModel = sharedViewModel)
} }
} }
} }

View file

@ -1,7 +1,6 @@
package mg.dot.feufaro.di package mg.dot.feufaro.di
import android.content.Context import android.content.Context
import mg.dot.feufaro.AndroidFileRepository // Import the actual Android implementation
import mg.dot.feufaro.config.AppConfig import mg.dot.feufaro.config.AppConfig
import org.koin.core.module.dsl.singleOf // Import for Koin DSL import org.koin.core.module.dsl.singleOf // Import for Koin DSL
import org.koin.core.module.dsl.bind // Import for Koin DSL import org.koin.core.module.dsl.bind // Import for Koin DSL
@ -12,15 +11,10 @@ import org.koin.android.ext.koin.androidContext // Import for androidContext()
val androidModule = module { val androidModule = module {
// Koin will automatically provide the Android Context because you called androidContext() in AndroidApp. // Koin will automatically provide the Android Context because you called androidContext() in AndroidApp.
// It then uses this Context to construct AndroidFileRepository. // It then uses this Context to construct AndroidFileRepository.
singleOf(::AndroidFileRepository) { bind<FileRepository>() }
single<AppConfig> { single<AppConfig> {
AppConfig( AppConfig(
transposeto = "C", transposeto = "C",
transposeasif = "C", transposeasif = "C",
buttonContainerColorHex = "#ff00f0",
buttonContentColorHex = "#ffffff",
buttonDisabledContainerColorHex = "#999999",
buttonDisabledContentColorHex = "#ffffff",
) )
} }

View file

@ -3,6 +3,7 @@ package mg.dot.feufaro
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
@ -26,6 +27,7 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -41,7 +43,10 @@ import kotlinx.coroutines.launch
import mg.dot.feufaro.solfa.LazyVerticalGridTUO import mg.dot.feufaro.solfa.LazyVerticalGridTUO
import mg.dot.feufaro.solfa.Solfa import mg.dot.feufaro.solfa.Solfa
import org.koin.compose.koinInject import org.koin.compose.koinInject
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.window.Popup
import kotlin.math.roundToInt
@Composable @Composable
@Preview @Preview
@ -53,6 +58,8 @@ fun App(sharedViewModel: SharedViewModel = SharedViewModel()
val currentDisplayConfig by displayConfigManager.displayConfig.collectAsState() val currentDisplayConfig by displayConfigManager.displayConfig.collectAsState()
// Load Configurations // Load Configurations
val configScope = CoroutineScope(Dispatchers.Default) val configScope = CoroutineScope(Dispatchers.Default)
var showContextualMenu by remember { mutableStateOf(false)}
var menuPosition by remember { mutableStateOf(Offset.Zero)}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
configScope.launch { configScope.launch {
@ -82,7 +89,34 @@ fun App(sharedViewModel: SharedViewModel = SharedViewModel()
MaterialTheme { MaterialTheme {
var showContent by remember { mutableStateOf(false) } var showContent by remember { mutableStateOf(false) }
var gridWidthPx by remember { mutableStateOf(0) } var gridWidthPx by remember { mutableStateOf(0) }
Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) { Column(
Modifier.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = {
offset ->
menuPosition = offset
showContextualMenu = true
}
)
},
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (showContextualMenu) {
Popup(
alignment = Alignment.TopStart,
offset = IntOffset(menuPosition.x.roundToInt(),
menuPosition.y.roundToInt()),
onDismissRequest = {
showContextualMenu = false
}
) {
ContextualMenu(onMenuItemClick = { item ->
println("Clicked: $item")
showContextualMenu = false
})
}
}
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()

View file

@ -0,0 +1,40 @@
package mg.dot.feufaro
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.List
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.material3.IconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.ui.unit.TextUnitType
@Composable
fun ContextualMenu (onMenuItemClick: (String) -> Unit){
Column(
modifier = Modifier
.background(Color.DarkGray.copy(alpha = 0.8f), RoundedCornerShape(8.dp))
.padding(8.dp)
) {
MenuItem(icon = Icons.Default.Add, text = "+") { onMenuItemClick("Ajouter")}
MenuItem(icon = Icons.Default.Edit, text = "!") { onMenuItemClick("Modifier")}
MenuItem(icon = Icons.Default.List, text = "-") { onMenuItemClick("Liste")}
}
}
@Composable
fun MenuItem(icon: androidx.compose.ui.graphics.vector.ImageVector, text: String, onClick: () -> Unit) {
IconButton(onClick = onClick) {
Column(horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally) {
Icon(icon, contentDescription = text, tint = Color.White)
}
}
}

View file

@ -1,13 +1,50 @@
package mg.dot.feufaro package mg.dot.feufaro
import java.io.IOException // Java.io.IOException est généralement partagée sur JVM/Android import java.io.IOException // Java.io.IOException est généralement partagée sur JVM/Android
import feufaro.composeapp.generated.resources.Res
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
// Définissez une expect interface. Elle spécifie le contrat de votre repository. // Définissez une expect interface. Elle spécifie le contrat de votre repository.
// Utilisez 'expect interface' car l'implémentation (actual) variera selon la plateforme. // Utilisez 'expect interface' car l'implémentation (actual) variera selon la plateforme.
expect interface FileRepository { interface FileRepository {
// Lecture de fichier ligne par ligne // Lecture de fichier ligne par ligne
suspend fun readFileLines(filePath: String): List<String> suspend fun readFileLines(filePath: String): List<String>
// Lecture de fichier entier en tant que String // Lecture de fichier entier en tant que String
suspend fun readFileContent(filePath: String): String suspend fun readFileContent(filePath: String): String
} }
// This is just a regular class that implements the common 'FileRepository' interface.
// It is NOT an 'actual' declaration of 'FileRepository'.
class CommonFileRepository : FileRepository { // IMPORTS AND IMPLEMENTS THE commonMain 'FileRepository' interface
override suspend fun readFileLines(filePath: String): List<String> = withContext(Dispatchers.IO) {
try {
when {
filePath.startsWith("assets://") -> {
readAssetFileLines(filePath)
}
else -> {
File(filePath).readLines()
}
}
} catch (e: IOException) {
throw IOException("Failed to read file or asset '$filePath'")
}
}
override suspend fun readFileContent(filePath: String): String = withContext(Dispatchers.IO) {
val lines = readFileLines(filePath)
lines.joinToString("\n") { it }
}
private suspend fun readAssetFileLines(assetFileName: String): List<String> {
return try {
Res.readBytes("files/"+assetFileName.removePrefix("assets://")).decodeToString().split("\n")
} catch (e: IOException) {
println("Could not read /"+assetFileName.removePrefix("assets://"))
throw IOException("Could not read asset file: $assetFileName", e)
}
}
}

View file

@ -29,6 +29,8 @@ open class SharedViewModel(): ViewModel() {
val tuoList: StateFlow<List<TimeUnitObject>> = _tuoList.asStateFlow() val tuoList: StateFlow<List<TimeUnitObject>> = _tuoList.asStateFlow()
private var _playlist = MutableStateFlow<List<String>>(viewModelScope, emptyList()) private var _playlist = MutableStateFlow<List<String>>(viewModelScope, emptyList())
val playlist: StateFlow<List<String>> = _playlist.asStateFlow() val playlist: StateFlow<List<String>> = _playlist.asStateFlow()
private var _nextPlayed = MutableStateFlow(viewModelScope, -1)
val nextPlayed: StateFlow<Int> = _nextPlayed.asStateFlow()
private val tempTimeUnitObjectList = mutableListOf<TimeUnitObject>() private val tempTimeUnitObjectList = mutableListOf<TimeUnitObject>()
var _hasMarker = MutableStateFlow<Boolean>(viewModelScope, false) var _hasMarker = MutableStateFlow<Boolean>(viewModelScope, false)
val hasMarker: StateFlow<Boolean> = _hasMarker.asStateFlow() val hasMarker: StateFlow<Boolean> = _hasMarker.asStateFlow()
@ -83,4 +85,12 @@ open class SharedViewModel(): ViewModel() {
_hasMarker.value = theHasMarker _hasMarker.value = theHasMarker
} }
fun playNext() {
val nextIndex = (_nextPlayed.value + 1) % _playlist.value.size
_nextPlayed.value = nextIndex
}
fun currentPlayed(): String {
val playlistIndex = _nextPlayed.value
return _playlist.value[playlistIndex]
}
} }

View file

@ -1,18 +1,22 @@
package mg.dot.feufaro.di package mg.dot.feufaro.di
import mg.dot.feufaro.CommonFileRepository
import mg.dot.feufaro.FileRepository import mg.dot.feufaro.FileRepository
import mg.dot.feufaro.SharedViewModel
import mg.dot.feufaro.DisplayConfigManager // Importez DisplayConfigManager import mg.dot.feufaro.DisplayConfigManager // Importez DisplayConfigManager
import mg.dot.feufaro.config.AppConfig import mg.dot.feufaro.config.AppConfig
import mg.dot.feufaro.musicXML.MusicXML import mg.dot.feufaro.musicXML.MusicXML
import org.koin.dsl.module import org.koin.dsl.module
import org.koin.core.module.dsl.singleOf import org.koin.core.module.dsl.singleOf
import org.koin.core.module.Module import org.koin.core.module.Module
import org.koin.core.module.dsl.viewModel
val commonModule = module { val commonModule = module {
// Déclarez FileRepository comme un singleton. // Déclarez FileRepository comme un singleton.
// L'implémentation concrète (actual) sera résolue par Koin en fonction de la plateforme. // L'implémentation concrète (actual) sera résolue par Koin en fonction de la plateforme.
// Pour Android, Koin injectera le Context que vous avez fourni via androidContext(). // Pour Android, Koin injectera le Context que vous avez fourni via androidContext().
singleOf(::MusicXML) singleOf(::MusicXML)
single<FileRepository> { CommonFileRepository() }
single { DisplayConfigManager(fileRepository = get())} single { DisplayConfigManager(fileRepository = get())}
viewModel { SharedViewModel() }
} }

View file

@ -36,7 +36,6 @@ class Solfa(val sharedViewModel: SharedViewModel, private val fileRepository: Fi
var nextLIndex: Int = -1 var nextLIndex: Int = -1
var inGroup: Boolean = false var inGroup: Boolean = false
var templateString: String = "" var templateString: String = ""
var nextPlayed = -1
private val meta: MutableMap<String, String> = mutableMapOf() private val meta: MutableMap<String, String> = mutableMapOf()
private val lyricsComment: MutableList<String> = mutableListOf() private val lyricsComment: MutableList<String> = mutableListOf()
@ -94,15 +93,16 @@ class Solfa(val sharedViewModel: SharedViewModel, private val fileRepository: Fi
} }
nextTimeUnitObject() nextTimeUnitObject()
} }
fun loadSolfa(sourceFileName: String) { fun loadSolfa() {
val sourceFileName: String = sharedViewModel.currentPlayed()
sharedViewModel.reset() sharedViewModel.reset()
parse(sourceFileName) parse(sourceFileName)
} }
fun loadNextInPlaylist() { fun loadNextInPlaylist() {
val playlist = sharedViewModel?.playlist?.value ?: listOf() val playlist = sharedViewModel.playlist.value
if (playlist.isNotEmpty()) { if (playlist.isNotEmpty()) {
nextPlayed = (nextPlayed + 1) % playlist.size sharedViewModel.playNext()
loadSolfa(playlist[nextPlayed]) loadSolfa()
} }
} }
fun parse(sourceFile: String) { fun parse(sourceFile: String) {

View file

@ -6,9 +6,9 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.material3.Text
import androidx.compose.material3.LocalTextStyle
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect

View file

@ -1,41 +0,0 @@
package mg.dot.feufaro
import feufaro.composeapp.generated.resources.Res
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.io.IOException
// This is just a regular class that implements the common 'FileRepository' interface.
// It is NOT an 'actual' declaration of 'FileRepository'.
class DesktopFileRepository : FileRepository { // IMPORTS AND IMPLEMENTS THE commonMain 'FileRepository' interface
override suspend fun readFileLines(filePath: String): List<String> = withContext(Dispatchers.IO) {
try {
when {
filePath.startsWith("assets://") -> {
readAssetFileLines(filePath)
}
else -> {
File(filePath).readLines()
}
}
} catch (e: IOException) {
throw IOException("Failed to read file or asset '$filePath'")
}
}
override suspend fun readFileContent(filePath: String): String = withContext(Dispatchers.IO) {
val lines = readFileLines(filePath)
lines.joinToString("\n") { it }
}
private suspend fun readAssetFileLines(assetFileName: String): List<String> {
return try {
Res.readBytes("files/"+assetFileName.removePrefix("assets://")).decodeToString().split("\n")
} catch (e: IOException) {
println("Could not read /"+assetFileName.removePrefix("assets://"))
throw IOException("Could not read asset file: $assetFileName", e)
}
}
}

View file

@ -1,11 +0,0 @@
package mg.dot.feufaro
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import mg.dot.feufaro.FileRepository
actual interface FileRepository {
actual suspend fun readFileLines(filePath: String): List<String>
actual suspend fun readFileContent(filePath: String): String
}

View file

@ -1,6 +1,5 @@
package mg.dot.feufaro.di package mg.dot.feufaro.di
import mg.dot.feufaro.DesktopFileRepository // Import the actual desktop implementation
import org.koin.core.module.dsl.singleOf // Import for Koin DSL import org.koin.core.module.dsl.singleOf // Import for Koin DSL
import org.koin.core.module.dsl.bind // Import for Koin DSL import org.koin.core.module.dsl.bind // Import for Koin DSL
import org.koin.dsl.module import org.koin.dsl.module
@ -10,11 +9,10 @@ import mg.dot.feufaro.config.AppConfig
val desktopModule = module { val desktopModule = module {
// When Koin is initialized on Desktop, it will use DesktopFileRepository // When Koin is initialized on Desktop, it will use DesktopFileRepository
// as the implementation for the FileRepository interface. // as the implementation for the FileRepository interface.
singleOf(::DesktopFileRepository) { bind<FileRepository>() }
single<AppConfig> { single<AppConfig> {
AppConfig( AppConfig(
transposeto = "C", transposeto = "C",
transposeasif = "C" transposeasif = "C"
) )
} }
} }

View file

@ -9,6 +9,7 @@ import org.koin.core.context.KoinContext
import org.koin.core.logger.Level import org.koin.core.logger.Level
import org.koin.compose.KoinContext import org.koin.compose.KoinContext
fun main() = application { fun main() = application {
startKoin { startKoin {
printLogger(Level.INFO) // Mettez Level.INFO pour voir les logs de Koin printLogger(Level.INFO) // Mettez Level.INFO pour voir les logs de Koin
@ -24,7 +25,8 @@ fun main() = application {
title = "Feufaro", title = "Feufaro",
) { ) {
KoinContext { KoinContext {
App() val sharedViewModel: SharedViewModel = SharedViewModel()
App(sharedViewModel = sharedViewModel)
} }
} }
} }

View file

@ -1,5 +1,5 @@
[versions] [versions]
agp = "8.7.3" agp = "8.10.1"
android-compileSdk = "35" android-compileSdk = "35"
android-minSdk = "24" android-minSdk = "24"
android-targetSdk = "35" android-targetSdk = "35"
@ -20,8 +20,11 @@ koin = "4.0.4"
core = "0.91.1" core = "0.91.1"
kmpObservableviewmodelCore = "1.0.0-BETA-3" kmpObservableviewmodelCore = "1.0.0-BETA-3"
kotlinxSerializationJson = "1.8.1" kotlinxSerializationJson = "1.8.1"
material3 = "1.3.2"
[libraries] [libraries]
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" }
koin-compose-viewmodel-navigation = { module = "io.insert-koin:koin-compose-viewmodel-navigation", version.ref = "koin" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
junit = { module = "junit:junit", version.ref = "junit" } junit = { module = "junit:junit", version.ref = "junit" }
@ -39,13 +42,15 @@ koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
koin-core-viewmodel = { module = "io.insert-koin:koin-core-viewmodel", version.ref = "koin" } koin-core-viewmodel = { module = "io.insert-koin:koin-core-viewmodel", version.ref = "koin" }
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
#20250704 koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" }
core = { module = "io.github.pdvrieze.xmlutil:core", version.ref = "core" } core = { module = "io.github.pdvrieze.xmlutil:core", version.ref = "core" }
#core-android = { module = "io.github.pdvrieze.xmlutil:core-android", version.ref = "core" } #core-android = { module = "io.github.pdvrieze.xmlutil:core-android", version.ref = "core" }
#core-jdk = { module = "io.github.pdvrieze.xmlutil:core-jdk", version.ref = "core" } #core-jdk = { module = "io.github.pdvrieze.xmlutil:core-jdk", version.ref = "core" }
serialization = { module = "io.github.pdvrieze.xmlutil:serialization", version.ref = "core" } serialization = { module = "io.github.pdvrieze.xmlutil:serialization", version.ref = "core" }
kmp-observableviewmodel-core = { module = "com.rickclephas.kmp:kmp-observableviewmodel-core", version.ref = "kmpObservableviewmodelCore" } kmp-observableviewmodel-core = { module = "com.rickclephas.kmp:kmp-observableviewmodel-core", version.ref = "kmpObservableviewmodelCore" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
androidx-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
[plugins] [plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" } androidApplication = { id = "com.android.application", version.ref = "agp" }

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME