From 932d22df804695d0e878f1a779da9581f7a64cba Mon Sep 17 00:00:00 2001 From: Arya Danesh Date: Wed, 15 May 2024 12:31:17 +0430 Subject: [PATCH] Koin SetUp Starting MVVM --- composeApp/build.gradle.kts | 12 ++- composeApp/src/desktopMain/kotlin/App.kt | 55 +++++--------- composeApp/src/desktopMain/kotlin/Greeting.kt | 7 -- composeApp/src/desktopMain/kotlin/Platform.kt | 5 -- .../desktopMain/kotlin/Player/PlayerImpl.kt | 27 ------- .../kotlin/Player/PlayerRepository.kt | 9 --- composeApp/src/desktopMain/kotlin/main.kt | 11 +++ .../player/data/repository/PlayerImpl.kt | 62 +++++++++++++++ .../kotlin/player/di/PlayerModule.kt | 43 +++++++++++ .../domain/repository/PlayerRepository.kt | 16 ++++ .../presentation/compose/SetupPlayer.kt | 48 ++++++++++++ .../presentation/viewmodel/PlayerViewModel.kt | 75 +++++++++++++++++++ .../kotlin/player/util/PlayerState.kt | 24 ++++++ .../data/repository}/RadioImpl.kt | 3 +- .../kotlin/radio/di/RadioModule.kt | 9 +++ .../domain/repository}/RadioRepository.kt | 5 +- 16 files changed, 320 insertions(+), 91 deletions(-) delete mode 100644 composeApp/src/desktopMain/kotlin/Greeting.kt delete mode 100644 composeApp/src/desktopMain/kotlin/Platform.kt delete mode 100644 composeApp/src/desktopMain/kotlin/Player/PlayerImpl.kt delete mode 100644 composeApp/src/desktopMain/kotlin/Player/PlayerRepository.kt create mode 100644 composeApp/src/desktopMain/kotlin/player/data/repository/PlayerImpl.kt create mode 100644 composeApp/src/desktopMain/kotlin/player/di/PlayerModule.kt create mode 100644 composeApp/src/desktopMain/kotlin/player/domain/repository/PlayerRepository.kt create mode 100644 composeApp/src/desktopMain/kotlin/player/presentation/compose/SetupPlayer.kt create mode 100644 composeApp/src/desktopMain/kotlin/player/presentation/viewmodel/PlayerViewModel.kt create mode 100644 composeApp/src/desktopMain/kotlin/player/util/PlayerState.kt rename composeApp/src/desktopMain/kotlin/{Radio => radio/data/repository}/RadioImpl.kt (94%) create mode 100644 composeApp/src/desktopMain/kotlin/radio/di/RadioModule.kt rename composeApp/src/desktopMain/kotlin/{Radio => radio/domain/repository}/RadioRepository.kt (71%) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index db78260..f4de5a4 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -9,7 +9,8 @@ kotlin { jvm("desktop") sourceSets { val desktopMain by getting - + val koin_version = "3.2.0" + commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) @@ -17,8 +18,15 @@ kotlin { implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) - implementation( "de.sfuhrm:radiobrowser4j:3.0.0") + implementation("de.sfuhrm:radiobrowser4j:3.0.0") implementation("com.darkrockstudios:mpfilepicker:3.1.0") + + implementation("io.insert-koin:koin-core:$koin_version") +// implementation("io.insert-koin:koin-core-coroutines:$koin_version") +// implementation("io.insert-koin:koin-compose:$koin_version") + implementation("io.insert-koin:koin-logger-slf4j:$koin_version") + + } desktopMain.dependencies { implementation(compose.desktop.currentOs) diff --git a/composeApp/src/desktopMain/kotlin/App.kt b/composeApp/src/desktopMain/kotlin/App.kt index 92b2f50..cce492e 100644 --- a/composeApp/src/desktopMain/kotlin/App.kt +++ b/composeApp/src/desktopMain/kotlin/App.kt @@ -1,5 +1,3 @@ -import Player.PlayerImpl -import Radio.RadioImpl import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells @@ -12,47 +10,34 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.darkrockstudios.libraries.mpfilepicker.FilePicker -import de.sfuhrm.radiobrowser4j.* -import org.jetbrains.compose.resources.ExperimentalResourceApi -import org.jetbrains.compose.ui.tooling.preview.Preview - +import de.sfuhrm.radiobrowser4j.Station +import org.koin.java.KoinJavaComponent +import org.koin.java.KoinJavaComponent.getKoin +import player.domain.repository.PlayerRepository +import player.presentation.compose.SetupPlayer +import player.presentation.viewmodel.PlayerViewModel +import player.util.PlayerState +import radio.data.repository.RadioImpl import java.io.File -@OptIn(ExperimentalResourceApi::class) @Composable -@Preview -fun App() { +fun App(player: PlayerViewModel = getKoin().get()) { MaterialTheme { - val playerDir: MutableState = remember { mutableStateOf(null) } - val showPicker = remember { mutableStateOf(false) } + + val lazyState = rememberLazyGridState() + val playerState by player.playerState.collectAsState() + + - FilePicker( - show = showPicker.value, fileExtensions = listOf("exe"), title = "mpc-hc64" - ) { platformFile -> - if (platformFile != null) { - playerDir.value = File(platformFile.path) - } - showPicker.value = false - } Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { - when (playerDir.value) { - null -> { - Column( - Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { + when (playerState) { - Text("Please Download And Select K-Lite Codec exe") - Button({ - showPicker.value = true - }) { - Text("Select") - } + is PlayerState.Empty -> { + + SetupPlayer() - } } else -> { @@ -63,8 +48,6 @@ fun App() { radioList.add(it) } } - val player = PlayerImpl(playerDir.value!!) - LazyVerticalGrid( modifier = Modifier.padding(10.dp).fillMaxSize(), @@ -73,7 +56,7 @@ fun App() { ) { items(radioList) { Card(modifier = Modifier.padding(10.dp).size(100.dp).clickable { - player.play(it.url) + player.play(it) }, backgroundColor = MaterialTheme.colors.surface, elevation = 10.dp) { Column( diff --git a/composeApp/src/desktopMain/kotlin/Greeting.kt b/composeApp/src/desktopMain/kotlin/Greeting.kt deleted file mode 100644 index 887d835..0000000 --- a/composeApp/src/desktopMain/kotlin/Greeting.kt +++ /dev/null @@ -1,7 +0,0 @@ -class Greeting { - private val platform = getPlatform() - - fun greet(): String { - return "Hello, ${platform.name}!" - } -} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/Platform.kt b/composeApp/src/desktopMain/kotlin/Platform.kt deleted file mode 100644 index 23d565b..0000000 --- a/composeApp/src/desktopMain/kotlin/Platform.kt +++ /dev/null @@ -1,5 +0,0 @@ -class JVMPlatform { - val name: String = "Java ${System.getProperty("java.version")}" -} - -fun getPlatform() = JVMPlatform() \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/Player/PlayerImpl.kt b/composeApp/src/desktopMain/kotlin/Player/PlayerImpl.kt deleted file mode 100644 index 6e80aa5..0000000 --- a/composeApp/src/desktopMain/kotlin/Player/PlayerImpl.kt +++ /dev/null @@ -1,27 +0,0 @@ -package Player - -import kotlinx.coroutines.* -import java.io.File - -class PlayerImpl constructor(val playerDir: File) : PlayerRepository { - private val scope = CoroutineScope(Dispatchers.IO) - private var runtime: Runtime = Runtime.getRuntime() - private var lastProcess: Process? = null - private var playerScope: Job? = null - - override fun play(link: String) { - if (playerScope!=null){ - lastProcess?.destroyForcibly()?.destroy() - } - playerScope = scope.launch { - lastProcess = runtime.exec("${playerDir.path} $link") - } - } - - override fun shutdown() { - println("Player.Player Cancelled") - runtime.addShutdownHook(Thread("Cancelled")) - } - - -} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/Player/PlayerRepository.kt b/composeApp/src/desktopMain/kotlin/Player/PlayerRepository.kt deleted file mode 100644 index 81bd28a..0000000 --- a/composeApp/src/desktopMain/kotlin/Player/PlayerRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package Player - -interface PlayerRepository { - - fun play(link: String) - - fun shutdown() - -} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/main.kt b/composeApp/src/desktopMain/kotlin/main.kt index 3d6ba21..f166927 100644 --- a/composeApp/src/desktopMain/kotlin/main.kt +++ b/composeApp/src/desktopMain/kotlin/main.kt @@ -1,9 +1,20 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.window.Window import androidx.compose.ui.window.application +import org.koin.core.context.startKoin +import player.di.PlayerModule +import radio.di.RadioModule + fun main() = application { val icon = painterResource("drawable/icon.png") + + + + startKoin { + modules(RadioModule) + modules(PlayerModule) + } Window( onCloseRequest = ::exitApplication, icon = icon, diff --git a/composeApp/src/desktopMain/kotlin/player/data/repository/PlayerImpl.kt b/composeApp/src/desktopMain/kotlin/player/data/repository/PlayerImpl.kt new file mode 100644 index 0000000..19d1e34 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/player/data/repository/PlayerImpl.kt @@ -0,0 +1,62 @@ +package player.data.repository + +import player.domain.repository.PlayerRepository +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow +import player.util.PlayerState +import java.io.File + +class PlayerImpl: PlayerRepository { + private var runtime: Runtime = Runtime.getRuntime() + private var playerDir: File? =null + + init { + println("PlayerImpl Created") + } + override fun setPlayerDir(playerEXE: String): Flow> { + return flow> { + + emit(PlayerState.Empty()) + try { + playerDir = File(playerEXE) + emit(PlayerState.Ready(playerEXE)) + }catch (e:Exception){ + emit(PlayerState.Error(e.message.toString())) + } + } + } + + override fun checkPlayerDefaultPath(): Flow> { + val defPath = "C:\\Program Files (x86)\\K-Lite Codec Pack\\MPC-HC64\\mpc-hc64.exe" + + return flow> { + emit(PlayerState.Empty()) + try { + val def = File(defPath) + if (def.exists() && def.isFile){ + playerDir = File(defPath) + emit(PlayerState.Ready(defPath)) + } + } catch (e: Exception) { + println(e.message) + } + } + } + + + override fun play(link: String): Process? { + return runtime.exec("${playerDir?.path} $link") + } + + + + override fun shutdown() { + println("Player.Player Cancelled") + runtime.addShutdownHook(Thread("Cancelled")) + } + + +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/player/di/PlayerModule.kt b/composeApp/src/desktopMain/kotlin/player/di/PlayerModule.kt new file mode 100644 index 0000000..af7847e --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/player/di/PlayerModule.kt @@ -0,0 +1,43 @@ +package player.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.createdAtStart +import org.koin.core.module.dsl.singleOf +import org.koin.core.module.dsl.withOptions +import player.data.repository.PlayerImpl +import org.koin.core.qualifier.named +import org.koin.dsl.module +import player.domain.repository.PlayerRepository +import player.presentation.viewmodel.PlayerViewModel + + +val PlayerModule = module(createdAtStart=true) { + +// single(named("playerRepository")) { +// PlayerImpl() +// } + + singleOf(::PlayerImpl) withOptions { + // definition options + named("playerRepository") + bind() + createdAtStart() + } + + + single{ + PlayerViewModel() + } withOptions { + // definition options + named("playerViewModel") + createdAtStart() + } + + + + +// single { PlayerImpl() as PlayerRepository } withOptions { +// named("playerRepository") +// createdAtStart() +// } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/player/domain/repository/PlayerRepository.kt b/composeApp/src/desktopMain/kotlin/player/domain/repository/PlayerRepository.kt new file mode 100644 index 0000000..5bb7eb8 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/player/domain/repository/PlayerRepository.kt @@ -0,0 +1,16 @@ +package player.domain.repository + +import kotlinx.coroutines.flow.Flow +import player.util.PlayerState + +interface PlayerRepository { + + fun setPlayerDir(playerEXE: String): Flow> + + fun checkPlayerDefaultPath(): Flow> + + fun play(link: String): Process? + + fun shutdown() + +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/player/presentation/compose/SetupPlayer.kt b/composeApp/src/desktopMain/kotlin/player/presentation/compose/SetupPlayer.kt new file mode 100644 index 0000000..cb8d465 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/player/presentation/compose/SetupPlayer.kt @@ -0,0 +1,48 @@ +package player.presentation.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.darkrockstudios.libraries.mpfilepicker.FilePicker +import org.koin.java.KoinJavaComponent +import player.domain.repository.PlayerRepository +import player.presentation.viewmodel.PlayerViewModel +import java.io.File + +@Composable +fun SetupPlayer(viewModel: PlayerViewModel = KoinJavaComponent.getKoin().get()){ + + val showPicker = remember { mutableStateOf(false) } + FilePicker( + show = showPicker.value, fileExtensions = listOf("exe"), title = "mpc-hc64" + ) { platformFile -> + if (platformFile != null) { + viewModel.setPlayerDir(platformFile.path) + } + showPicker.value = false + } + + Column( + Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + + Text("Please Download And Select K-Lite Codec exe") + Button({ + showPicker.value = true + }) { + Text("Select") + } + + } + +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/player/presentation/viewmodel/PlayerViewModel.kt b/composeApp/src/desktopMain/kotlin/player/presentation/viewmodel/PlayerViewModel.kt new file mode 100644 index 0000000..4d7181b --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/player/presentation/viewmodel/PlayerViewModel.kt @@ -0,0 +1,75 @@ +package player.presentation.viewmodel + +import de.sfuhrm.radiobrowser4j.Station +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flow +import org.koin.java.KoinJavaComponent +import player.domain.repository.PlayerRepository +import player.util.PlayerReadyState +import player.util.PlayerState +import java.io.File + +class PlayerViewModel(private val player: PlayerRepository = KoinJavaComponent.getKoin().get()) { + + private var lastProcess: Process? = null + private var playerScope: Job? = null + private val scope = CoroutineScope(Dispatchers.IO) + + private val _playerState: MutableStateFlow> = MutableStateFlow(PlayerState.Empty()) + val playerState: StateFlow> = _playerState + + private val _playerReadyState: MutableStateFlow> = MutableStateFlow(PlayerReadyState.Waiting()) + val playerReadyState: StateFlow> = _playerReadyState + + init { + checkPlayerDefaultPath() + } + + fun setPlayerDir(playerEXE: String) { + scope.launch { + player.setPlayerDir(playerEXE).collectLatest { + _playerState.value = it + } + } + } + + private fun checkPlayerDefaultPath(){ + scope.launch { + player.checkPlayerDefaultPath().collectLatest { + _playerState.value = it + } + } + } + + fun play(station : Station) { +// if (playerScope != null) { +// lastProcess?.destroyForcibly()?.destroy() +// } + playerScope = scope.launch { + + flow> { + emit(PlayerReadyState.Loading()) + try { + lastProcess = player.play(station.url) + emit(PlayerReadyState.Playing(station)) + }catch (e:Exception){ + println(e.message) + emit(PlayerReadyState.Error(e.message.toString())) + } + }.collectLatest { + _playerReadyState.value = it + } + } + } + + fun shutdown() { + player.shutdown() + lastProcess?.destroyForcibly()?.destroy() + playerScope?.cancel() + scope.cancel() + } + +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/player/util/PlayerState.kt b/composeApp/src/desktopMain/kotlin/player/util/PlayerState.kt new file mode 100644 index 0000000..1ff4376 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/player/util/PlayerState.kt @@ -0,0 +1,24 @@ +package player.util + +sealed class PlayerState( + val data: T? = null, + val errorMsg: String? = null +) { + class Ready(data: T?) : PlayerState(data = data) + + class Error(msg: String) : PlayerState(errorMsg = msg) + + class Empty : PlayerState() +} + +sealed class PlayerReadyState( + val data: T? = null, + val errorMsg: String? = null +) { + class Playing(data: T?) : PlayerReadyState(data = data) + class Loading : PlayerReadyState() + class Error(msg: String) : PlayerReadyState(errorMsg = msg) + class Waiting : PlayerReadyState() + + +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/Radio/RadioImpl.kt b/composeApp/src/desktopMain/kotlin/radio/data/repository/RadioImpl.kt similarity index 94% rename from composeApp/src/desktopMain/kotlin/Radio/RadioImpl.kt rename to composeApp/src/desktopMain/kotlin/radio/data/repository/RadioImpl.kt index 41ef3df..56bcc5f 100644 --- a/composeApp/src/desktopMain/kotlin/Radio/RadioImpl.kt +++ b/composeApp/src/desktopMain/kotlin/radio/data/repository/RadioImpl.kt @@ -1,5 +1,6 @@ -package Radio +package radio.data.repository +import radio.domain.repository.RadioRepository import de.sfuhrm.radiobrowser4j.ConnectionParams import de.sfuhrm.radiobrowser4j.EndpointDiscovery import de.sfuhrm.radiobrowser4j.RadioBrowser diff --git a/composeApp/src/desktopMain/kotlin/radio/di/RadioModule.kt b/composeApp/src/desktopMain/kotlin/radio/di/RadioModule.kt new file mode 100644 index 0000000..d11165f --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/radio/di/RadioModule.kt @@ -0,0 +1,9 @@ +package radio.di + +import radio.data.repository.RadioImpl +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +val RadioModule = module { + singleOf(::RadioImpl) +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/Radio/RadioRepository.kt b/composeApp/src/desktopMain/kotlin/radio/domain/repository/RadioRepository.kt similarity index 71% rename from composeApp/src/desktopMain/kotlin/Radio/RadioRepository.kt rename to composeApp/src/desktopMain/kotlin/radio/domain/repository/RadioRepository.kt index 4139785..8f5e0a8 100644 --- a/composeApp/src/desktopMain/kotlin/Radio/RadioRepository.kt +++ b/composeApp/src/desktopMain/kotlin/radio/domain/repository/RadioRepository.kt @@ -1,9 +1,6 @@ -package Radio +package radio.domain.repository -import de.sfuhrm.radiobrowser4j.RadioBrowser import de.sfuhrm.radiobrowser4j.Station -import java.util.* -import java.util.stream.Stream interface RadioRepository {