Shared KMP core and UI, switch to Koin
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
package ru.stenfredd.sleepyowl.core.common
|
||||
|
||||
import android.content.Context
|
||||
import java.io.File
|
||||
|
||||
/** The places the TV version always used: `files/datastore` (as `preferencesDataStore`) and `files`. */
|
||||
fun androidAppDirs(context: Context) = AppDirs(preferences = File(context.filesDir, "datastore"), files = context.filesDir)
|
||||
@@ -0,0 +1,5 @@
|
||||
package ru.stenfredd.sleepyowl.core.common
|
||||
|
||||
import android.os.SystemClock
|
||||
|
||||
actual fun monotonicMs(): Long = SystemClock.elapsedRealtime()
|
||||
@@ -0,0 +1,9 @@
|
||||
package ru.stenfredd.sleepyowl.core.common
|
||||
|
||||
import timber.log.Timber
|
||||
|
||||
internal actual fun logEnabled() = Timber.treeCount > 0
|
||||
|
||||
internal actual fun platformLog(priority: Int, tag: String, t: Throwable?, message: String, args: Array<out Any?>) {
|
||||
Timber.tag(tag).log(priority, t, message, *args)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package ru.stenfredd.sleepyowl.core.common
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import java.io.File
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Where the app keeps its files on this platform.
|
||||
*
|
||||
* [preferences]: DataStore files (`<name>.preferences_pb`) — accounts, settings, remembered tracks. [files]: data that
|
||||
* can be fetched again but should outlive a restart, such as the home screen snapshot.
|
||||
*/
|
||||
class AppDirs(val preferences: File, val files: File)
|
||||
|
||||
/** The DataStore of each preferences file: DataStore allows one instance per file, so everyone gets it here. */
|
||||
class PreferenceStores(private val dirs: AppDirs) {
|
||||
private val stores = ConcurrentHashMap<String, DataStore<Preferences>>()
|
||||
|
||||
operator fun get(name: String): DataStore<Preferences> = stores.computeIfAbsent(name) {
|
||||
PreferenceDataStoreFactory.create(produceFile = { File(dirs.preferences, "$name.preferences_pb") })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package ru.stenfredd.sleepyowl.core.common
|
||||
|
||||
/** Milliseconds of a clock that only goes forward; on Android it keeps counting while the device sleeps. */
|
||||
expect fun monotonicMs(): Long
|
||||
@@ -0,0 +1,41 @@
|
||||
package ru.stenfredd.sleepyowl.core.common
|
||||
|
||||
/**
|
||||
* The app's log on every platform. Like Timber: [message] is a `String.format` pattern, formatted only when [args]
|
||||
* are given, and the tag is the calling class. Android writes through Timber, desktop to stderr.
|
||||
*/
|
||||
object Log {
|
||||
const val DEBUG = 3
|
||||
const val INFO = 4
|
||||
const val WARN = 5
|
||||
const val ERROR = 6
|
||||
|
||||
fun d(message: String, vararg args: Any?) = write(DEBUG, null, message, args)
|
||||
fun d(t: Throwable?, message: String, vararg args: Any?) = write(DEBUG, t, message, args)
|
||||
fun i(message: String, vararg args: Any?) = write(INFO, null, message, args)
|
||||
fun i(t: Throwable?, message: String, vararg args: Any?) = write(INFO, t, message, args)
|
||||
fun w(message: String, vararg args: Any?) = write(WARN, null, message, args)
|
||||
fun w(t: Throwable?, message: String, vararg args: Any?) = write(WARN, t, message, args)
|
||||
fun e(message: String, vararg args: Any?) = write(ERROR, null, message, args)
|
||||
fun e(t: Throwable?, message: String, vararg args: Any?) = write(ERROR, t, message, args)
|
||||
|
||||
private fun write(priority: Int, t: Throwable?, message: String, args: Array<out Any?>) {
|
||||
if (logEnabled()) platformLog(priority, callerTag(), t, message, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple name of the class that called [Log], without `$1` of lambdas — the tag Timber's DebugTree gave. Our own
|
||||
* frames are told apart by the class name at run time, as Timber does: R8 may merge [Log] into another class.
|
||||
*/
|
||||
private fun callerTag(): String {
|
||||
val own = javaClass.name
|
||||
val caller = Throwable().stackTrace.firstOrNull { it.className != own } ?: return "SleepyOwl"
|
||||
return caller.className.substringAfterLast('.').replace(ANONYMOUS_CLASS, "")
|
||||
}
|
||||
|
||||
private val ANONYMOUS_CLASS = Regex("(\\$\\d+)+$")
|
||||
}
|
||||
|
||||
internal expect fun logEnabled(): Boolean
|
||||
|
||||
internal expect fun platformLog(priority: Int, tag: String, t: Throwable?, message: String, args: Array<out Any?>)
|
||||
@@ -0,0 +1,10 @@
|
||||
package ru.stenfredd.sleepyowl.core.common
|
||||
|
||||
import java.io.File
|
||||
|
||||
/** XDG places: settings in `~/.config/sleepyowl`, refetchable data in `~/.cache/sleepyowl`. */
|
||||
fun desktopAppDirs(): AppDirs {
|
||||
fun xdg(variable: String, default: String) =
|
||||
File(System.getenv(variable)?.takeIf { it.isNotBlank() } ?: (System.getProperty("user.home") + "/" + default), "sleepyowl")
|
||||
return AppDirs(preferences = xdg("XDG_CONFIG_HOME", ".config"), files = xdg("XDG_CACHE_HOME", ".cache"))
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package ru.stenfredd.sleepyowl.core.common
|
||||
|
||||
actual fun monotonicMs(): Long = System.nanoTime() / 1_000_000
|
||||
@@ -0,0 +1,18 @@
|
||||
package ru.stenfredd.sleepyowl.core.common
|
||||
|
||||
import java.time.LocalTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
private val TIME = DateTimeFormatter.ofPattern("HH:mm:ss.SSS")
|
||||
private val LEVELS = mapOf(Log.DEBUG to 'D', Log.INFO to 'I', Log.WARN to 'W', Log.ERROR to 'E')
|
||||
|
||||
internal actual fun logEnabled() = true
|
||||
|
||||
internal actual fun platformLog(priority: Int, tag: String, t: Throwable?, message: String, args: Array<out Any?>) {
|
||||
val text = if (args.isEmpty()) message else message.format(*args)
|
||||
val line = "${LocalTime.now().format(TIME)} ${LEVELS[priority]} $tag: $text"
|
||||
synchronized(System.err) {
|
||||
System.err.println(line)
|
||||
t?.printStackTrace()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user