Custom.kt 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. package eu.mjdev.desktop.extensions
  2. import androidx.compose.runtime.*
  3. import androidx.compose.runtime.snapshots.SnapshotStateList
  4. import androidx.compose.ui.graphics.NativePaint
  5. import androidx.compose.ui.unit.Dp
  6. import androidx.compose.ui.unit.DpSize
  7. import com.google.gson.Gson
  8. import eu.mjdev.desktop.data.DesktopFile
  9. import eu.mjdev.desktop.extensions.Compose.rememberState
  10. import eu.mjdev.desktop.extensions.Locale.toLocale
  11. import eu.mjdev.desktop.helpers.streams.ResourceStream
  12. import kotlinx.coroutines.Dispatchers
  13. import kotlinx.coroutines.delay
  14. import kotlinx.coroutines.flow.catch
  15. import kotlinx.coroutines.flow.channelFlow
  16. import kotlinx.coroutines.flow.flow
  17. import kotlinx.coroutines.flow.flowOn
  18. import kotlinx.coroutines.launch
  19. import org.jetbrains.skia.FilterBlurMode.NORMAL
  20. import org.jetbrains.skia.MaskFilter.Companion.makeBlur
  21. import java.io.BufferedReader
  22. import java.io.File
  23. import java.text.DateFormat
  24. import java.util.*
  25. import java.util.Locale
  26. import kotlin.coroutines.CoroutineContext
  27. import kotlin.jvm.optionals.getOrNull
  28. @Suppress("MemberVisibilityCanBePrivate", "FunctionName", "unused")
  29. object Custom {
  30. val ProcessHandle.command: String
  31. get() = info().command().getOrNull().orEmpty()
  32. // val ProcessHandle.commandLine: String
  33. // get() = info().commandLine().getOrNull().orEmpty()
  34. val Process.consoleOutput: String
  35. get() {
  36. val sj = StringJoiner(System.lineSeparator())
  37. val bfr = BufferedReader(inputReader())
  38. bfr.lines().iterator().forEachRemaining { s: String? -> sj.add(s) }
  39. return sj.toString()
  40. }
  41. inline fun <reified T> Any?.orElse(
  42. block: () -> T
  43. ) = if (this == null) block() else this as T
  44. val Char.isPrintable: Boolean
  45. get() {
  46. val block = Character.UnicodeBlock.of(this)
  47. return (!Character.isISOControl(this)) &&
  48. this != java.awt.event.KeyEvent.CHAR_UNDEFINED &&
  49. block != null &&
  50. block != Character.UnicodeBlock.SPECIALS
  51. }
  52. fun <T> SnapshotStateList<T>.replaceLast(data: T) {
  53. this[this.size - 1] = data
  54. }
  55. fun File.listFiles(
  56. ext: String? = null
  57. ): List<File> = if (exists()) listFiles().let { list ->
  58. when {
  59. list == null -> emptyList()
  60. ext != null -> list.filter { f -> f.extension == ext }
  61. else -> list.toList()
  62. }
  63. } else emptyList()
  64. fun File.listFilesFiltered(
  65. ext: String? = null,
  66. predicate: (File) -> Boolean
  67. ) = listFiles(ext).filter(predicate)
  68. fun File.listFilesOnly(
  69. ext: String? = null,
  70. ) = listFilesFiltered(ext) { f -> !f.isDirectory }
  71. fun List<File>.sortedByName() =
  72. sortedBy { f -> f.name }
  73. // fun List<File>.sortedByNameDescending() =
  74. // sortedByDescending { f -> f.name }
  75. inline fun <R> File.listFiles(
  76. ext: String? = null,
  77. mapper: (File) -> R
  78. ): List<R> = listFiles(ext).map(mapper)
  79. // inline fun <R : Comparable<R>> File.listFilesSortedBy(
  80. // ext: String? = null,
  81. // crossinline selector: (File) -> R?
  82. // ): List<File> = listFiles(ext).sortedBy(selector)
  83. fun File.listDesktopFiles(
  84. ext: String = DesktopFile.EXTENSION
  85. ): List<DesktopFile> = listFiles(ext) { f -> DesktopFile(f) }
  86. inline fun <reified T : Any> MutableList<T>.addIfNotExists(
  87. element: T,
  88. equal: (e1: T, e2: T) -> Boolean = { e1, e2 -> e1 == e2 }
  89. ) = any { e -> equal(e, element) }.also { contains -> if (!contains) add(element) }
  90. operator fun File.get(name: String) =
  91. resolve(name)
  92. fun File.readTextAsLocale(): Locale =
  93. if (exists()) text.toLocale() else Locale.ENGLISH
  94. val File.all
  95. get() = listFiles()?.toList() ?: emptyList<File>()
  96. val File.filesOnly
  97. get() = listFiles()?.filter { !it.isDirectory }?.toList() ?: emptyList<File>()
  98. val File.dirsOnly
  99. get() = listFiles()?.filter { it.isDirectory }?.toList() ?: emptyList<File>()
  100. val File.lines: List<String>
  101. get() = runCatching { if (exists()) readLines(Charsets.UTF_8) else null }.getOrNull() ?: emptyList()
  102. val File.text: String
  103. get() = runCatching { if (exists()) readText(Charsets.UTF_8) else null }.getOrNull().orEmpty()
  104. val File.textAsLocale: Locale
  105. get() = if (exists()) readTextAsLocale() else Locale.ENGLISH
  106. val File.desktopFiles: List<DesktopFile>
  107. get() = listDesktopFiles()
  108. // inline fun <T, K> distinctList(
  109. // vararg lists: List<T>,
  110. // selector: (T) -> K
  111. // ): List<T> = mutableListOf<T>().apply {
  112. // lists.forEach { l -> addAll(l) }
  113. // }.distinctBy(selector)
  114. fun String.notStartsWith(
  115. prefix: String,
  116. ignoreCase: Boolean = false
  117. ) = !startsWith(prefix, ignoreCase)
  118. // fun String.trimIsEmpty() =
  119. // trim().isEmpty()
  120. // fun String.trimIsNotEmpty() =
  121. // trim().isNotEmpty()
  122. // fun State<String>.trimIsEmpty() =
  123. // value.trim().isEmpty()
  124. fun State<String>.trimIsNotEmpty() =
  125. value.trim().isNotEmpty()
  126. // fun String.trimStartsWith(
  127. // prefix: String,
  128. // ignoreCase: Boolean = false
  129. // ) = trim().startsWith(prefix, ignoreCase)
  130. // fun String.trimNotStartsWith(
  131. // prefix: String,
  132. // ignoreCase: Boolean = false
  133. // ) = !trim().startsWith(prefix, ignoreCase)
  134. fun ParsedList(
  135. value: String?,
  136. delimiter: String = ";"
  137. ): MutableList<String> = value?.split(delimiter)?.toMutableList() ?: mutableListOf()
  138. fun ParsedBoolean(
  139. value: String?,
  140. defaultValue: Boolean = false,
  141. ) = value.orEmpty().trim().lowercase().let { b ->
  142. when (b) {
  143. "true" -> true
  144. "ano" -> true
  145. "yes" -> true
  146. "enabled" -> true
  147. "*" -> true
  148. "1" -> true
  149. else -> defaultValue
  150. }
  151. }
  152. fun ParsedString(
  153. value: Any?,
  154. defaultValue: String = ""
  155. ) = value?.toString() ?: defaultValue
  156. @Composable
  157. fun <T> flowBlock(
  158. initial: T,
  159. delay: Long = 0L,
  160. coroutineContext: CoroutineContext = Dispatchers.IO,
  161. onError: (Throwable) -> Unit = { e -> e.printStackTrace() },
  162. block: suspend () -> T,
  163. ): MutableState<T> {
  164. val result = remember { mutableStateOf(initial) }
  165. var state by rememberState(0L)
  166. LaunchedEffect(state) {
  167. flow {
  168. emit(block())
  169. }.catch { t ->
  170. onError(t)
  171. }.flowOn(coroutineContext).collect { r ->
  172. result.value = r
  173. }
  174. if (delay > 0L) {
  175. delay(delay)
  176. state = System.currentTimeMillis()
  177. }
  178. }
  179. return result
  180. }
  181. // todo move to uiState
  182. @Composable
  183. fun <T> flowBlock(
  184. initial: T,
  185. key: Any,
  186. coroutineContext: CoroutineContext = Dispatchers.IO,
  187. onError: (Throwable) -> Unit = { e -> e.printStackTrace() },
  188. block: suspend () -> T,
  189. ): MutableState<T> {
  190. println("flow block called")
  191. val result = remember { mutableStateOf(initial) }
  192. LaunchedEffect(key) {
  193. println("launching for new key")
  194. flow {
  195. emit(block())
  196. }.catch { t ->
  197. onError(t)
  198. }.flowOn(coroutineContext).collect { r ->
  199. println("got new result")
  200. result.value = r
  201. }
  202. }
  203. return result
  204. }
  205. fun NativePaint.setMaskFilter(
  206. blurRadius: Float
  207. ) {
  208. maskFilter = makeBlur(NORMAL, blurRadius / 2, true)
  209. }
  210. val dateFlow
  211. @Composable
  212. get() = channelFlow {
  213. launch {
  214. var date = "1.1.1970"
  215. do {
  216. DateFormat.getDateInstance().format(Date()).also { t ->
  217. if (date != t) {
  218. date = t
  219. send(date)
  220. }
  221. }
  222. delay(5000L)
  223. } while (true)
  224. }
  225. }.collectAsState(initial = "")
  226. val timeFlow
  227. @Composable
  228. get() = channelFlow {
  229. launch {
  230. var time = "00:00:00"
  231. do {
  232. DateFormat.getTimeInstance().format(Date()).also { t ->
  233. if (time != t) {
  234. time = t
  235. send(time)
  236. }
  237. }
  238. delay(200L)
  239. } while (true)
  240. }
  241. }.collectAsState(initial = "")
  242. fun loadKey(key: String): String = runCatching {
  243. ResourceStream("keys/$key.key").string
  244. }.getOrNull().orEmpty()
  245. inline fun <reified T> String.jsonToList(): List<T> = runCatching {
  246. Gson().fromJson(this, List::class.java).mapNotNull { item -> item as? T }
  247. }.getOrNull() ?: emptyList()
  248. inline fun <reified T> String.to(): T? = runCatching {
  249. Gson().fromJson(this, T::class.java)
  250. }.getOrNull()
  251. val Process.command: String?
  252. get() = info().command().getOrNull()
  253. operator fun DpSize.plus(dp: Dp) =
  254. copy(width = width + dp, height = height + dp)
  255. operator fun DpSize.minus(dp: Dp) =
  256. copy(width = width - dp, height = height - dp)
  257. // operator fun PaddingValues.plus(dp: Dp) =
  258. // copy(left = width + dp, height = height + dp)
  259. // operator fun PaddingValues.minus(dp: Dp) =
  260. // copy(width = width - dp, height = height - dp)
  261. fun MutableState<Boolean>.toggle() {
  262. value = !value
  263. }
  264. }