init projects

This commit is contained in:
2026-06-17 18:58:41 +03:00
parent 8543c4e241
commit 21a768edd5
505 changed files with 19998 additions and 93 deletions
+2
View File
@@ -0,0 +1,2 @@
/build
/bin
+17
View File
@@ -0,0 +1,17 @@
plugins {
alias(libs.plugins.convention.kmp)
alias(libs.plugins.convention.android.lib)
alias(libs.plugins.convention.serialization)
alias(libs.plugins.convention.koin)
}
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation(libs.multiplatform.settings)
}
}
}
android { namespace = "ru.shadowsparky" }
}
@@ -0,0 +1,18 @@
package ru.shadowsparky.data
import android.content.ClipData
import android.content.Context
import org.koin.core.annotation.Factory
import ru.shadowsparky.domain.Clip
import ru.shadowsparky.domain.ClipboardManager
@Factory
class AndroidClipboardManager(private val context: Context) : ClipboardManager {
private val service by lazy {
context.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
}
override suspend fun setText(clip: Clip) {
service.setPrimaryClip(ClipData.newPlainText(clip.title, clip.text))
}
}
@@ -0,0 +1,17 @@
package ru.shadowsparky.data
import org.koin.core.annotation.Single
import ru.shadowsparky.domain.Log
private typealias Impl = android.util.Log
@Single
class AndroidLog : Log {
override fun d(tag: String, msg: String?, e: Throwable?) {
Impl.d(tag, msg, e)
}
override fun e(tag: String, e: Throwable?, msg: String?) {
Impl.e(tag, msg, e)
}
}
@@ -0,0 +1,15 @@
package ru.shadowsparky.data
import android.app.Application
import android.content.Context
import com.russhwolf.settings.Settings
import com.russhwolf.settings.SharedPreferencesSettings
import org.koin.core.annotation.Single
@Single
class AndroidSettingsFactory(private val app: Application) : SettingsFactory {
override fun create(name: String): Settings {
val prefs = app.getSharedPreferences(name, Context.MODE_PRIVATE)
return SharedPreferencesSettings(prefs)
}
}
@@ -0,0 +1,37 @@
package ru.shadowsparky
import kotlinx.serialization.json.Json
import org.koin.core.Koin
import org.koin.core.annotation.ComponentScan
import org.koin.core.annotation.Configuration
import org.koin.core.annotation.Module
import org.koin.core.annotation.Single
import org.koin.core.component.KoinComponent
import ru.shadowsparky.data.SettingsFactory
import ru.shadowsparky.data.SettingsKeyValueStorage
import ru.shadowsparky.domain.DispatcherProvider
import ru.shadowsparky.domain.KeyValueStorage
val koin: Koin get() = object : KoinComponent {}.getKoin()
@Module
@ComponentScan
@Configuration
class CommonModule {
@Single
fun provideJson(): Json {
return Json {
ignoreUnknownKeys = true
encodeDefaults = true
}
}
@Single
fun provideSettings(
dispatcherProvider: DispatcherProvider,
settingsFactory: SettingsFactory
): KeyValueStorage {
return SettingsKeyValueStorage(dispatcherProvider, "app_settings", settingsFactory)
}
}
@@ -0,0 +1,14 @@
package ru.shadowsparky.data
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import org.koin.core.annotation.Singleton
import ru.shadowsparky.domain.DispatcherProvider
@Singleton
class DispatcherProviderImpl : DispatcherProvider {
override val io: CoroutineDispatcher = Dispatchers.Default
override val main: CoroutineDispatcher = Dispatchers.Main
override val unconfined: CoroutineDispatcher = Dispatchers.Unconfined
override val default: CoroutineDispatcher = Dispatchers.Default
}
@@ -0,0 +1,110 @@
package ru.shadowsparky.data
import com.russhwolf.settings.ObservableSettings
import com.russhwolf.settings.Settings
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import ru.shadowsparky.domain.DispatcherProvider
import ru.shadowsparky.domain.KeyValueStorage
private typealias NotifyCallback = ((String?) -> Unit)?
class SettingsKeyValueStorage(
private val dispatcherProvider: DispatcherProvider,
private val name: String,
private val settingsFactory: SettingsFactory
) : KeyValueStorage {
private val impl: BaseImpl by lazy {
val settings = settingsFactory.create(name)
if (settings is ObservableSettings) {
DefaultImpl(settings)
} else {
FallbackImpl(settings)
}
}
override fun get(key: String): Flow<String?> {
return impl.get(key)
}
override suspend fun remove(key: String) = withContext(dispatcherProvider.io) {
impl.remove(key)
}
override suspend fun put(key: String, value: String) = withContext(dispatcherProvider.io) {
impl.put(key, value)
}
private abstract class BaseImpl(private val settings: Settings) {
open fun remove(key: String) {
settings.remove(key)
}
open fun put(key: String, value: String) {
settings.putString(key, value)
}
abstract fun get(key: String): Flow<String?>
}
private class DefaultImpl(private val settings: ObservableSettings) : BaseImpl(settings) {
override fun get(key: String): Flow<String?> {
return callbackFlow {
val listener: (String?) -> Unit = {
launch { send(it) }
}
send(settings.getStringOrNull(key))
val disposable = settings.addStringOrNullListener(key, listener)
awaitClose { disposable.deactivate() }
}
}
}
private class FallbackImpl(private val settings: Settings) : BaseImpl(settings) {
private val listenerMap = hashMapOf<String, MutableList<NotifyCallback>>()
override fun get(key: String): Flow<String?> = callbackFlow {
val listener: (String?) -> Unit = {
launch { send(it) }
}
send(settings.getStringOrNull(key))
put(key, listener)
awaitClose {
remove(key, listener)
}
}
private fun put(key: String, listener: NotifyCallback) {
val list = listenerMap[key] ?: mutableListOf()
list.add(listener)
listenerMap[key] = list
}
private fun remove(key: String, listener: NotifyCallback) {
val list = listenerMap[key] ?: return
val index = list.indexOf(listener)
if (index >= 0) {
list.removeAt(index)
}
listenerMap[key] = list
}
override fun remove(key: String) {
super.remove(key)
listenerMap[key]?.forEach {
it?.invoke(null)
}
}
override fun put(key: String, value: String) {
super.put(key, value)
listenerMap[key]?.forEach {
it?.invoke(value)
}
}
}
}
@@ -0,0 +1,7 @@
package ru.shadowsparky.data
import com.russhwolf.settings.Settings
interface SettingsFactory {
fun create(name: String): Settings
}
@@ -0,0 +1,6 @@
package ru.shadowsparky.domain
interface AppConfig {
fun isDebug(): Boolean
fun isBackend(): Boolean = false
}
@@ -0,0 +1,7 @@
package ru.shadowsparky.domain
interface ClipboardManager {
suspend fun setText(clip: Clip)
}
data class Clip(val title: String, val text: String)
@@ -0,0 +1,10 @@
package ru.shadowsparky.domain
import kotlinx.coroutines.CoroutineDispatcher
interface DispatcherProvider {
val io: CoroutineDispatcher
val main: CoroutineDispatcher
val unconfined: CoroutineDispatcher
val default: CoroutineDispatcher
}
@@ -0,0 +1,15 @@
package ru.shadowsparky.domain
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
interface KeyValueStorage {
fun get(key: String): Flow<String?>
fun getBoolean(key: String): Flow<Boolean?> = get(key).map { it?.toBoolean() }
fun getInt(key: String): Flow<Int?> = get(key).map { it?.toInt() }
suspend fun remove(key: String)
suspend fun put(key: String, value: String)
suspend fun putBoolean(key: String, value: Boolean) = put(key, value.toString())
suspend fun putInt(key: String, value: Int) = put(key, value.toString())
}
@@ -0,0 +1,6 @@
package ru.shadowsparky.domain
interface Log {
fun d(tag: String, msg: String? = null, e: Throwable? = null)
fun e(tag: String, e: Throwable? = null, msg: String? = null)
}
@@ -0,0 +1,18 @@
package ru.shadowsparky.data
import org.koin.core.annotation.Single
import ru.shadowsparky.domain.Log
@Single
class DesktopLog : Log {
override fun d(tag: String, msg: String?, e: Throwable?) {
println("$tag $msg")
e?.printStackTrace()
}
override fun e(tag: String, e: Throwable?, msg: String?) {
println("$tag $msg")
e?.printStackTrace()
}
}
@@ -0,0 +1,13 @@
package ru.shadowsparky.data
import com.russhwolf.settings.PreferencesSettings
import com.russhwolf.settings.Settings
import org.koin.core.annotation.Single
import java.util.prefs.Preferences
@Single
class DesktopSettingsFactory : SettingsFactory {
override fun create(name: String): Settings {
return PreferencesSettings(Preferences.userRoot().node(name))
}
}
@@ -0,0 +1,17 @@
package ru.shadowsparky.data
import org.koin.core.annotation.Single
import ru.shadowsparky.domain.Log
@Single
class WasmLog : Log {
override fun d(tag: String, msg: String?, e: Throwable?) {
println("$tag $msg")
e?.printStackTrace()
}
override fun e(tag: String, e: Throwable?, msg: String?) {
println("$tag $msg")
e?.printStackTrace()
}
}
@@ -0,0 +1,12 @@
package ru.shadowsparky.data
import com.russhwolf.settings.Settings
import com.russhwolf.settings.StorageSettings
import org.koin.core.annotation.Single
@Single
class WasmSettingsFactory : SettingsFactory {
override fun create(name: String): Settings {
return StorageSettings()
}
}