Refactor code to support AGP 9.0

This commit is contained in:
2025-12-20 20:53:13 +03:00
Unverified
parent 8151e0ca0d
commit b18af36b3e
133 changed files with 255 additions and 125 deletions
@@ -0,0 +1,42 @@
@file:Suppress("NOTHING_TO_INLINE")
package com.pr0gramm3r101.components
import android.widget.ImageView
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.DefaultAlpha
import androidx.compose.ui.graphics.asAndroidColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.viewinterop.AndroidView
import com.pr0gramm3r101.utils.asAndroidScaleType
@Composable
inline fun Mipmap(
@DrawableRes id: Int,
contentDescription: String?,
modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Fit,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null
) {
val update: (ImageView) -> Unit = {
it.setImageResource(id)
it.contentDescription = contentDescription
it.alpha = alpha
it.colorFilter = colorFilter?.asAndroidColorFilter()
it.scaleType = contentScale.asAndroidScaleType()
}
AndroidView(
modifier = modifier,
factory = {
ImageView(it).apply {
update(this)
}
},
update = update
)
}
@@ -0,0 +1,9 @@
@file:Suppress("NOTHING_TO_INLINE")
package com.pr0gramm3r101.utils
import androidx.compose.material3.adaptive.currentWindowSize
import androidx.compose.runtime.Composable
@Composable
actual inline fun currentWindowSize() = currentWindowSize()
@@ -0,0 +1,99 @@
package com.pr0gramm3r101.utils;
import android.content.Intent;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCaller;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityOptionsCompat;
public final class BetterActivityResult<I, R> {
/**
* Register activity result using a {@link ActivityResultContract} and an in-place activity result callback like
* the default approach. You can still customise callback using {@link #launch(Object, OnActivityResult)}.
*/
@NonNull
public static <I, R> BetterActivityResult<I, R> registerForActivityResult(
@NonNull ActivityResultCaller caller,
@NonNull ActivityResultContract<I, R> contract,
@Nullable OnActivityResult<R> onActivityResult) {
return new BetterActivityResult<>(caller, contract, onActivityResult);
}
/**
* Same as {@link #registerForActivityResult(ActivityResultCaller, ActivityResultContract, OnActivityResult)} except
* the last argument is set to {@code null}.
*/
@NonNull
public static <I, R> BetterActivityResult<I, R> registerForActivityResult(
@NonNull ActivityResultCaller caller,
@NonNull ActivityResultContract<I, R> contract) {
return registerForActivityResult(caller, contract, null);
}
/**
* Specialised method for launching new activities.
*/
@NonNull
public static BetterActivityResult<Intent, ActivityResult> registerActivityForResult(
@NonNull ActivityResultCaller caller) {
return registerForActivityResult(caller, new ActivityResultContracts.StartActivityForResult());
}
/**
* Callback interface
*/
@FunctionalInterface
public interface OnActivityResult<O> {
/**
* Called after receiving a result from the target activity
*/
void onActivityResult(O result);
}
private final ActivityResultLauncher<I> launcher;
@Nullable
private OnActivityResult<R> onActivityResult;
private BetterActivityResult(@NonNull ActivityResultCaller caller,
@NonNull ActivityResultContract<I, R> contract,
@Nullable OnActivityResult<R> onActivityResult) {
this.onActivityResult = onActivityResult;
this.launcher = caller.registerForActivityResult(contract, this::callOnActivityResult);
}
/**
* Launch activity, same as {@link ActivityResultLauncher#launch(Object)} except that it allows a callback
* executed after receiving a result from the target activity.
*/
public void launch(I input, @Nullable OnActivityResult<R> onActivityResult) {
this.onActivityResult = onActivityResult;
launcher.launch(input);
}
public void launch(I input, ActivityOptionsCompat options, @Nullable OnActivityResult<R> onActivityResult) {
this.onActivityResult = onActivityResult;
launcher.launch(input, options);
}
/**
* Same as {@link #launch(Object, OnActivityResult)} with last parameter set to {@code null}.
* @noinspection unused
*/
public void launch(I input) {
launch(input, (OnActivityResult<R>) null);
}
/** @noinspection unused*/
public void launch(I input, ActivityOptionsCompat options) {
launch(input, options, null);
}
private void callOnActivityResult(R result) {
if (onActivityResult != null) onActivityResult.onActivityResult(result);
}
}
@@ -0,0 +1,43 @@
package com.pr0gramm3r101.utils
import androidx.annotation.IntDef
import org.jetbrains.annotations.Contract
object JobIdManager {
const val JOB_TYPE_CHANNEL_PROGRAMS: Int = 1
const val JOB_TYPE_CHANNEL_METADATA: Int = 2
const val JOB_TYPE_CHANNEL_DELETION: Int = 3
const val JOB_TYPE_CHANNEL_LOGGER: Int = 4
const val JOB_TYPE_USER_PREFS: Int = 11
const val JOB_TYPE_USER_BEHAVIOR: Int = 21
//16-1 for short. Adjust per your needs
private const val JOB_TYPE_SHIFTS = 15
@Contract(pure = true)
fun getJobId(@JobType jobType: Int, objectId: Int): Int {
if (objectId > 0 && objectId < (1 shl JOB_TYPE_SHIFTS)) {
return (jobType shl JOB_TYPE_SHIFTS) + objectId
} else {
throw IllegalArgumentException(String.format(
"objectId %s must be between %s and %s",
objectId, 0, (1 shl JOB_TYPE_SHIFTS)
))
}
}
@IntDef(
value = [
JOB_TYPE_CHANNEL_PROGRAMS,
JOB_TYPE_CHANNEL_METADATA,
JOB_TYPE_CHANNEL_DELETION,
JOB_TYPE_CHANNEL_LOGGER,
JOB_TYPE_USER_PREFS,
JOB_TYPE_USER_BEHAVIOR
]
)
@Retention(AnnotationRetention.SOURCE)
private annotation class JobType
}
@@ -0,0 +1,18 @@
package com.pr0gramm3r101.utils
import java.util.concurrent.Executor
@Suppress("MemberVisibilityCanBePrivate")
class NoParallelExecutor: Executor {
val isRunning get() = thread != null
var thread: Thread? = null
override fun execute(command: Runnable) {
if (!isRunning) {
thread = async {
command.run()
thread = null
}
}
}
}
@@ -0,0 +1,489 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package com.pr0gramm3r101.utils
import android.content.ClipData
import android.content.ComponentName
import android.content.Intent
import android.graphics.Rect
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.os.PersistableBundle
import java.io.InputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.OutputStream
import java.io.Serial
import java.io.Serializable
fun Intent.serialize(outputStream: OutputStream) = SerializableIntent.serialize(this, outputStream)
fun Intent.toSerializableIntent() = SerializableIntent(this)
class SerializableIntent(): Intent(), Serializable {
companion object {
@Serial
const val serialVersionUID = 1241895592857245245L
fun deserialize(inputStream: InputStream): Intent {
ObjectInputStream(inputStream).use {
val obj = it.readObject() as SerializableIntent
return obj.toIntent()
}
}
fun serialize(intent: Intent, outputStream: OutputStream) {
SerializableIntent(intent).serialize(outputStream)
}
}
constructor(intent: Intent): this() {
_data = intent.data
_package = intent.`package`
_flags = intent.flags
_type = intent.type
_action = intent.action
_categories.addAll(intent.categories)
_component = intent.component
_extras = intent.extras
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
_identifier = intent.identifier
}
_sourceBounds = intent.sourceBounds
}
@Transient
var _data: Uri? = null
private var __data: String? = null
var _package: String? = null
var _flags: Int = 0
var _type: String? = null
val _scheme get() = _data?.scheme
var _action: String? = null
val _categories = mutableSetOf<String>()
@Transient
var _component: ComponentName? = null
private var __component_packageName: String? = null
private var __component_className: String? = null
@Transient
var _extras: Bundle? = null
private var __extras_persistableBundle: PersistableBundle? = null
var _identifier: String? = null
@Transient
var _sourceBounds: Rect? = null
private var __sourceBounds_top: Int? = null
private var __sourceBounds_bottom: Int? = null
private var __sourceBounds_left: Int? = null
private var __sourceBounds_right: Int? = null
fun prepareSerialize() {
__data = _data?.toString()
__extras_persistableBundle = _extras?.toPersistableBundle()
__component_packageName = _component?.packageName
__component_className = _component?.className
__sourceBounds_top = _sourceBounds?.top
__sourceBounds_bottom = _sourceBounds?.bottom
__sourceBounds_left = _sourceBounds?.left
__sourceBounds_right = _sourceBounds?.right
}
fun serialize(outputStream: OutputStream) {
prepareSerialize()
ObjectOutputStream(outputStream).use {
it.writeObject(this@SerializableIntent)
}
}
fun toIntent(): Intent {
val intent = Intent()
intent.setDataAndType(_data, _type)
intent.`package` = _package
intent.flags = _flags
intent.action = _action
_categories.forEach {
intent.addCategory(it)
}
intent.component = _component
_extras?.let { intent.putExtras(it) }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
intent.identifier = _identifier
}
_sourceBounds = Rect(
__sourceBounds_left?: 0,
__sourceBounds_top?: 0,
__sourceBounds_right?: 0,
__sourceBounds_bottom?: 0
)
return intent
}
// OVERRIDES
override fun getData() = _data
override fun setData(uri: Uri?): Intent {
this._data = uri
return this
}
override fun getPackage() = _package
override fun setPackage(pkg: String?): Intent {
this._package = pkg
return this
}
override fun getFlags() = _flags
override fun setFlags(flags: Int): Intent {
this._flags = flags
return this
}
override fun addFlags(flags: Int): Intent {
this._flags = this._flags or flags
return this
}
override fun getType() = _type
override fun setType(type: String?): Intent {
this._type = type
return this
}
override fun getScheme() = _scheme
override fun getAction() = _action
override fun setAction(action: String?): Intent {
this._action = action
return this
}
override fun addCategory(category: String): Intent {
_categories.add(category)
return this
}
override fun getCategories() = _categories
override fun hasCategory(category: String) = _categories.contains(category)
override fun removeCategory(category: String) {
_categories.remove(category)
}
override fun getClipData() = throw UnsupportedOperationException("Not supported in SerializableIntent")
override fun setClipData(clipData: ClipData?) = throw UnsupportedOperationException("Not supported in SerializableIntent")
override fun getComponent(): ComponentName? = _component
override fun setComponent(component: ComponentName?): Intent {
this._component = component
return this
}
override fun getDataString() = _data?.toString()
override fun getExtras() = _extras
override fun putExtras(extras: Bundle): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putAll(extras)
return this
}
override fun putExtras(src: Intent): Intent {
if (src.extras != null) {
putExtras(src.extras!!)
}
return this
}
override fun putExtra(name: String, value: Array<out CharSequence>?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putCharSequenceArray(name, value)
return this
}
override fun putExtra(name: String, value: Array<out Parcelable>?): Intent {
return this // Stub!
}
override fun putExtra(name: String, value: Array<out String>?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putStringArray(name, value)
return this
}
override fun putExtra(name: String, value: Boolean): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putBoolean(name, value)
return this
}
override fun putExtra(name: String, value: BooleanArray?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putBooleanArray(name, value)
return this
}
override fun putExtra(name: String, value: Bundle?): Intent {
return this // Stub!
}
override fun putExtra(name: String, value: Byte): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putByte(name, value)
return this
}
override fun putExtra(name: String, value: ByteArray?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putByteArray(name, value)
return this
}
override fun putExtra(name: String?, value: Char): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putChar(name, value)
return this
}
override fun putExtra(name: String, value: CharArray?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putCharArray(name, value)
return this
}
override fun putExtra(name: String, value: CharSequence?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putCharSequence(name, value)
return this
}
override fun putExtra(name: String, value: Double): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putDouble(name, value)
return this
}
override fun putExtra(name: String, value: DoubleArray?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putDoubleArray(name, value)
return this
}
override fun putExtra(name: String?, value: Float): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putFloat(name, value)
return this
}
override fun putExtra(name: String, value: FloatArray?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putFloatArray(name, value)
return this
}
override fun putExtra(name: String, value: Int): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putInt(name, value)
return this
}
override fun putExtra(name: String, value: IntArray?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putIntArray(name, value)
return this
}
override fun putExtra(name: String, value: Long): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putLong(name, value)
return this
}
override fun putExtra(name: String, value: LongArray?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putLongArray(name, value)
return this
}
override fun putExtra(name: String, value: Parcelable?): Intent {
return this // Stub!
}
override fun putExtra(name: String, value: Short): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putShort(name, value)
return this
}
override fun putExtra(name: String, value: ShortArray?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putShortArray(name, value)
return this
}
override fun putExtra(name: String, value: String?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putString(name, value)
return this
}
override fun putExtra(name: String, value: Serializable?): Intent {
return this // Stub!
}
override fun putStringArrayListExtra(name: String, value: ArrayList<String>?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putStringArrayList(name, value)
return this
}
override fun putIntegerArrayListExtra(name: String, value: ArrayList<Int>?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putIntegerArrayList(name, value)
return this
}
override fun putParcelableArrayListExtra(name: String?, value: java.util.ArrayList<out Parcelable>?): Intent {
return this // Stub!
}
override fun putCharSequenceArrayListExtra(name: String?, value: java.util.ArrayList<CharSequence>?): Intent {
if (this._extras == null) {
this._extras = Bundle()
}
this._extras!!.putCharSequenceArrayList(name, value)
return this
}
override fun getBooleanExtra(name: String, defaultValue: Boolean): Boolean {
return _extras?.getBoolean(name, defaultValue) ?: defaultValue
}
override fun getBooleanArrayExtra(name: String): BooleanArray? {
return _extras?.getBooleanArray(name)
}
override fun getByteExtra(name: String, defaultValue: Byte): Byte {
return _extras?.getByte(name, defaultValue) ?: defaultValue
}
override fun getByteArrayExtra(name: String?): ByteArray? {
return _extras?.getByteArray(name)
}
override fun getCharExtra(name: String, defaultValue: Char): Char {
return _extras?.getChar(name, defaultValue) ?: defaultValue
}
override fun getCharArrayExtra(name: String?): CharArray? {
return _extras?.getCharArray(name)
}
override fun getDoubleExtra(name: String, defaultValue: Double): Double {
return _extras?.getDouble(name, defaultValue) ?: defaultValue
}
override fun getDoubleArrayExtra(name: String?): DoubleArray? {
return _extras?.getDoubleArray(name)
}
override fun getFloatExtra(name: String, defaultValue: Float): Float {
return _extras?.getFloat(name, defaultValue) ?: defaultValue
}
override fun getFloatArrayExtra(name: String?): FloatArray? {
return _extras?.getFloatArray(name)
}
override fun getIntExtra(name: String, defaultValue: Int): Int {
return _extras?.getInt(name, defaultValue) ?: defaultValue
}
override fun getIntArrayExtra(name: String?): IntArray? {
return _extras?.getIntArray(name)
}
override fun getLongExtra(name: String, defaultValue: Long): Long {
return _extras?.getLong(name, defaultValue) ?: defaultValue
}
override fun getLongArrayExtra(name: String): LongArray? {
return _extras?.getLongArray(name)
}
@Deprecated("Deprecated in Java")
override fun getSerializableExtra(name: String): Serializable? {
return null // Stub!
}
override fun getStringArrayListExtra(name: String): ArrayList<String>? {
return _extras?.getStringArrayList(name)
}
override fun getIntegerArrayListExtra(name: String): ArrayList<Int>? {
return _extras?.getIntegerArrayList(name)
}
override fun getCharSequenceArrayListExtra(name: String): ArrayList<CharSequence>? {
return _extras?.getCharSequenceArrayList(name)
}
override fun <T : Serializable?> getSerializableExtra(name: String?, clazz: Class<T>): T? {
return null // Stub!
}
override fun getBundleExtra(name: String): Bundle? {
return null // Stub!
}
override fun getStringExtra(name: String): String? {
return _extras?.getString(name)
}
override fun getShortExtra(name: String, defaultValue: Short): Short {
return _extras?.getShort(name, defaultValue) ?: defaultValue
}
override fun getShortArrayExtra(name: String): ShortArray? {
return _extras?.getShortArray(name)
}
override fun getStringArrayExtra(name: String): Array<String>? {
return _extras?.getStringArray(name)
}
override fun getCharSequenceExtra(name: String): CharSequence? {
return _extras?.getCharSequence(name)
}
override fun <T : Any?> getParcelableExtra(name: String?, clazz: Class<T>): T? {
return null // Stub!
}
@Deprecated("Deprecated in Java")
override fun <T : Parcelable?> getParcelableExtra(name: String?): T? {
return null // Stub!
}
override fun getCharSequenceArrayExtra(name: String): Array<CharSequence>? {
return _extras?.getCharSequenceArray(name)
}
override fun <T : Any?> getParcelableArrayExtra(name: String?, clazz: Class<T>): Array<T>? {
return null // Stub!
}
@Deprecated("Deprecated in Java")
override fun getParcelableArrayExtra(name: String): Array<Parcelable>? {
return null // Stub!
}
override fun <T : Any?> getParcelableArrayListExtra(name: String?, clazz: Class<out T>): java.util.ArrayList<T>? {
return null // Stub!
}
@Deprecated("Deprecated in Java")
override fun <T : Parcelable?> getParcelableArrayListExtra(name: String?): java.util.ArrayList<T>? {
return null // Stub!
}
override fun getIdentifier() = _identifier
override fun setIdentifier(identifier: String?): Intent {
this._identifier = identifier
return this
}
override fun getSelector(): Intent? {
return null
}
override fun setSelector(selector: Intent?) {
// Stub!
}
override fun getSourceBounds() = _sourceBounds
override fun setSourceBounds(sourceBounds: Rect?) {
this._sourceBounds = sourceBounds
}
override fun isMismatchingFilter() = false
}
@@ -0,0 +1,13 @@
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
package com.pr0gramm3r101.utils
import android.os.AsyncTask
abstract class SimpleAsyncTask: AsyncTask<Unit, Unit, Unit>() {
final override fun doInBackground(vararg params: Unit) = run()
abstract fun run()
abstract fun postRun()
final override fun onProgressUpdate(vararg values: Unit) {}
final override fun onPostExecute(result: Unit?) = postRun()
}
@@ -0,0 +1,832 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package com.pr0gramm3r101.utils
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.app.KeyguardManager
import android.app.Notification
import android.content.ComponentName
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_MAIN
import android.content.Intent.CATEGORY_LAUNCHER
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.content.res.Resources
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.hardware.usb.UsbEndpoint
import android.hardware.usb.UsbInterface
import android.net.Uri
import android.os.BaseBundle
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.os.PersistableBundle
import android.provider.OpenableColumns
import android.service.quicksettings.Tile
import android.util.Base64
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.view.ViewTreeObserver.OnPreDrawListener
import android.widget.ImageView.ScaleType
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultCaller
import androidx.annotation.AttrRes
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.annotation.RequiresPermission
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.isImeVisible
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Dp
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.window.core.layout.WindowSizeClass
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.io.IOException
import java.io.InputStream
import java.io.Serializable
import java.lang.ref.WeakReference
import java.security.SecureRandom
import java.util.regex.Pattern
import javax.crypto.Cipher
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.PBEKeySpec
import kotlin.random.Random
import kotlin.random.nextInt
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
@OptIn(ExperimentalLayoutApi::class)
actual fun Modifier.clearFocusOnKeyboardDismiss(): Modifier = composed {
var isFocused by remember { mutableStateOf(false) }
var keyboardAppearedSinceLastFocused by remember { mutableStateOf(false) }
if (isFocused) {
val imeIsVisible = WindowInsets.isImeVisible
val focusManager = LocalFocusManager.current
LaunchedEffect(imeIsVisible) {
if (imeIsVisible) {
@Suppress("AssignedValueIsNeverRead")
keyboardAppearedSinceLastFocused = true
} else if (keyboardAppearedSinceLastFocused) {
focusManager.clearFocus()
}
}
}
onFocusEvent {
if (isFocused != it.isFocused) {
isFocused = it.isFocused
if (isFocused) {
keyboardAppearedSinceLastFocused = false
}
}
}
}
@SuppressLint("ComposableNaming")
@Composable
actual fun ToggleNavScrimEffect(enabled: Boolean) {
val context = (LocalContext() as Activity)
LaunchedEffect(enabled) {
runCatching {
val window = context.window
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = enabled
}
}
}
}
val screenWidth: Int inline get() = Resources.getSystem().displayMetrics.widthPixels
@Suppress("unused")
val screenHeight: Int inline get() = Resources.getSystem().displayMetrics.heightPixels
fun appName(context: Context, packageName: String): String? {
try {
val packageManager = context.packageManager
val applicationInfo = packageManager.getApplicationInfo(
packageName, PackageManager.GET_META_DATA
)
val appName = packageManager.getApplicationLabel(applicationInfo) as String
return appName
} catch (_: PackageManager.NameNotFoundException) {
return null
}
}
fun Context.homeScreen() {
val intentHome = Intent(ACTION_MAIN)
intentHome.addCategory(Intent.CATEGORY_HOME)
intentHome.flags = FLAG_ACTIVITY_NEW_TASK
startActivity(intentHome)
}
inline fun async(noinline exec: () -> Unit) = Thread(exec).apply { start() }
inline fun Tile.configure(apply: Tile.() -> Unit) {
apply.invoke(this)
updateTile()
}
inline fun View.addOneTimeOnPreDrawListener(crossinline listener: () -> Boolean) {
viewTreeObserver.addOnPreDrawListener(object: OnPreDrawListener {
override fun onPreDraw(): Boolean {
viewTreeObserver.removeOnPreDrawListener(this)
return listener()
}
})
}
fun Context.launchFiles(): Boolean {
val primaryStorageUri = "content://com.android.externalstorage.documents/root/primary".toUri()
fun launchIntentWithComponent(action: String, componentName: ComponentName? = null): Boolean {
val intent = Intent(action, primaryStorageUri)
if (componentName != null) {
intent.setComponent(componentName)
}
try {
startActivity(intent)
return true
} catch (_: Throwable) {
return false
}
}
fun intent1() = launchIntentWithComponent(
Intent.ACTION_VIEW,
ComponentName(
"com.google.android.documentsui",
"com.android.documentsui.files.FilesActivity"
)
)
fun intent2() = launchIntentWithComponent(
Intent.ACTION_VIEW,
ComponentName(
"com.android.documentsui",
"com.android.documentsui.files.FilesActivity"
)
)
fun intent3() = launchIntentWithComponent(
Intent.ACTION_VIEW,
ComponentName(
"com.android.documentsui",
"com.android.documentsui.FilesActivity"
)
)
fun intent4() = launchIntentWithComponent(Intent.ACTION_VIEW)
fun intent5() = launchIntentWithComponent("android.provider.action.BROWSE")
fun intent6() = launchIntentWithComponent("android.provider.action.BROWSE_DOCUMENT_ROOT")
return intent1() || intent2() || intent3() || intent4() || intent5() || intent6()
}
inline fun Fragment.getSystemService(name: String): Any? = requireActivity().getSystemService(name)
inline fun <T> Fragment.getSystemService(cls: Class<T>): T? = requireActivity().getSystemService(cls)
inline fun <T: Any> Context.getSystemService(cls: KClass<T>): T? = getSystemService(cls.java)
inline fun <T: Any> Fragment.getSystemService(cls: KClass<T>): T? = requireActivity().getSystemService(cls)
typealias ActivityLauncher = BetterActivityResult<Intent, ActivityResult>
val ActivityResultCaller.activityResultLauncher get() = BetterActivityResult.registerActivityForResult(this)
@Suppress("unused")
fun getOpenDocumentIntent(vararg fileTypes: String) =
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
if (fileTypes.size == 1) {
type = fileTypes[0]
} else {
type = "*/*"
putExtra(Intent.EXTRA_MIME_TYPES, fileTypes)
}
}
fun Context.getFileName(uri: Uri): String {
if (uri.scheme == "content") {
contentResolver.query(
uri,
null,
null,
null,
null
)!!.use { cursor ->
if (cursor.moveToFirst()) {
return cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
}
}
} else {
throw UnsupportedOperationException("Cannot process non-\"content://\" URIs")
}
return uri.lastPathSegment.let { name ->
name!!
val matcher = Pattern.compile("\\w+:(.*/)*(.*)").matcher(name)
if (matcher.find()) {
matcher.group(2)
} else {
name
}
}
}
@Suppress("unused")
inline fun Fragment.getFileName(uri: Uri) = requireActivity().getFileName(uri)
@Suppress("unused")
val UsbInterface.endpointList: List<UsbEndpoint> get() {
val list = mutableListOf<UsbEndpoint>()
for (i in 0 until endpointCount) {
list.add(getEndpoint(i))
}
return list.toList()
}
fun Activity.resolveAttr(@AttrRes id: Int): Int? {
val typedValue = TypedValue()
if (theme.resolveAttribute(
id,
typedValue,
true
)) {
val resId = typedValue.resourceId
return if (resId != 0) resId else null
} else {
return null
}
}
@Suppress("unused", "MemberVisibilityCanBePrivate")
class QuickAlertDialogBuilder(context: Context): MaterialAlertDialogBuilder(context) {
fun title(title: CharSequence): QuickAlertDialogBuilder {
setTitle(title)
return this
}
fun title(@StringRes titleRes: Int): QuickAlertDialogBuilder {
setTitle(titleRes)
return this
}
fun message(message: CharSequence): QuickAlertDialogBuilder {
setMessage(message)
return this
}
fun message(@StringRes messageRes: Int): QuickAlertDialogBuilder {
setMessage(messageRes)
return this
}
fun body(body: CharSequence) = message(body)
fun body(@StringRes bodyRes: Int) = message(bodyRes)
inline fun positiveButton(
text: CharSequence,
crossinline listener: () -> Unit
) : QuickAlertDialogBuilder {
setPositiveButton(text) { _, _ -> listener() }
return this
}
inline fun positiveButton(@StringRes textRes: Int, crossinline listener: () -> Unit): QuickAlertDialogBuilder {
setPositiveButton(textRes) { _, _ -> listener() }
return this
}
inline fun positiveButton(text: CharSequence, listener: Nothing? = null): QuickAlertDialogBuilder {
setPositiveButton(text, null)
return this
}
inline fun positiveButton(@StringRes text: Int, listener: Nothing? = null): QuickAlertDialogBuilder {
setPositiveButton(text, null)
return this
}
inline fun negativeButton(text: CharSequence, crossinline listener: () -> Unit): QuickAlertDialogBuilder {
setNegativeButton(text) { _, _ -> listener() }
return this
}
inline fun negativeButton(@StringRes textRes: Int, crossinline listener: () -> Unit): QuickAlertDialogBuilder {
setNegativeButton(textRes) { _, _ -> listener() }
return this
}
inline fun negativeButton(text: CharSequence, listener: Nothing? = null): QuickAlertDialogBuilder {
setNegativeButton(text, null)
return this
}
inline fun negativeButton(@StringRes text: Int, listener: Nothing? = null): QuickAlertDialogBuilder {
setNegativeButton(text, null)
return this
}
inline fun neutralButton(text: CharSequence, crossinline listener: () -> Unit): QuickAlertDialogBuilder {
setNeutralButton(text) { _, _ -> listener() }
return this
}
inline fun neutralButton(@StringRes textRes: Int, crossinline listener: () -> Unit): QuickAlertDialogBuilder {
setNeutralButton(textRes) { _, _ -> listener() }
return this
}
inline fun neutralButton(text: CharSequence, listener: Nothing? = null): QuickAlertDialogBuilder {
setNeutralButton(text, null)
return this
}
inline fun neutralButton(@StringRes text: Int, listener: Nothing? = null): QuickAlertDialogBuilder {
setNeutralButton(text, null)
return this
}
inline fun onCancel(crossinline listener: () -> Unit): QuickAlertDialogBuilder {
setOnCancelListener { listener() }
return this
}
inline fun cancelable(value: Boolean): QuickAlertDialogBuilder {
setCancelable(value)
return this
}
}
inline fun Activity.alertDialog(crossinline config: QuickAlertDialogBuilder.() -> Unit): AlertDialog {
val builder = QuickAlertDialogBuilder(this)
config(builder)
return builder.show()
}
@Suppress("unused")
inline fun Fragment.alertDialog(crossinline config: QuickAlertDialogBuilder.() -> Unit)
= requireActivity().alertDialog(config)
@Suppress("unused")
class AuthenticationConfig {
var success: ((BiometricPrompt.AuthenticationResult) -> Unit)? = null
var fail: (() -> Unit)? = null
var error: ((Int, String) -> Unit)? = null
var always: (() -> Unit)? = null
fun success(block: (BiometricPrompt.AuthenticationResult) -> Unit) {
success = block
}
inline fun ail(noinline block: () -> Unit) {
fail = block
}
inline fun error(noinline block: (Int, String) -> Unit) {
error = block
}
inline fun always(noinline block: () -> Unit) {
always = block
}
lateinit var title: String
var subtitle: String? = null
lateinit var negativeButtonText: String
}
inline fun FragmentActivity.requestAuthentication(crossinline callback: AuthenticationConfig.() -> Unit): BiometricPrompt {
val config = AuthenticationConfig()
callback(config)
try {
config.title
config.negativeButtonText
} catch (_: UninitializedPropertyAccessException) {
throw IllegalStateException("Required fields haven't been set")
}
val executor = ContextCompat.getMainExecutor(this)
val biometricPrompt = BiometricPrompt(
this,
executor,
object: BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
config.fail?.invoke()
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
config.success?.invoke(result)
config.always?.invoke()
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
config.error?.invoke(errorCode, "$errString")
config.always?.invoke()
}
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder().apply {
setTitle(config.title)
setSubtitle(config.subtitle)
setNegativeButtonText(config.negativeButtonText)
setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
setConfirmationRequired(false)
}.build()
biometricPrompt.authenticate(promptInfo)
return biometricPrompt
}
fun ContentScale.asAndroidScaleType(): ScaleType? {
return when (this) {
ContentScale.Fit -> ScaleType.FIT_CENTER
ContentScale.Crop -> ScaleType.CENTER_CROP
ContentScale.FillWidth,
ContentScale.FillHeight,
ContentScale.FillBounds -> ScaleType.FIT_XY
ContentScale.Inside -> ScaleType.CENTER_INSIDE
else -> null
}
}
inline fun Context.openUrl(url: Uri) {
startActivity(Intent(Intent.ACTION_VIEW, url))
}
inline fun Context.openUrl(url: String) {
openUrl(
url.let {
val regex = "^[\\w-_]://".toRegex()
var th = it
if (!regex.containsMatchIn(th)) {
th = "https://$th"
}
th
}.toUri()
)
}
inline fun ComponentActivity.ComposeView(crossinline init: @Composable () -> Unit) =
ComposeView(this).apply {
setContent {
init()
}
}
inline fun backCallback(enabled: Boolean = true, crossinline callback: OnBackPressedCallback.() -> Unit)
= object: OnBackPressedCallback(enabled) {
override fun handleOnBackPressed() {
callback(this)
}
}
fun String.encrypt(password: String): String {
val salt = ByteArray(16).also { SecureRandom().nextBytes(it) }
val spec = PBEKeySpec(password.toCharArray(), salt, 65536, 256)
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
val secretKey = factory.generateSecret(spec)
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
val iv = ByteArray(cipher.blockSize).also { SecureRandom().nextBytes(it) }
val params = IvParameterSpec(iv)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, params)
val encryptedBytes = cipher.doFinal(this.toByteArray())
return "${Base64.encodeToString(salt, Base64.DEFAULT)}:${Base64.encodeToString(iv, Base64.DEFAULT)}:${Base64.encodeToString(encryptedBytes, Base64.DEFAULT)}"
}
fun String.decrypt(password: String): String {
val parts = this.split(":")
val salt = Base64.decode(parts[0], Base64.DEFAULT)
val iv = Base64.decode(parts[1], Base64.DEFAULT)
val encryptedBytes = Base64.decode(parts[2], Base64.DEFAULT)
val spec = PBEKeySpec(password.toCharArray(), salt, 65536, 256)
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
val secretKey = factory.generateSecret(spec)
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
val params = IvParameterSpec(iv)
cipher.init(Cipher.DECRYPT_MODE, secretKey, params)
val decryptedBytes = cipher.doFinal(encryptedBytes)
return String(decryptedBytes)
}
@Suppress("unused")
inline fun waitUntil(timeout: Long = 0, condition: () -> Boolean): Boolean {
val time = System.currentTimeMillis()
while (!condition()) {
if (timeout > 0 && System.currentTimeMillis() - time > timeout) {
return false
}
}
return true
}
inline fun waitWhile(timeout: Long = 0, condition: () -> Boolean): Boolean {
val time = System.currentTimeMillis()
while (condition()) {
if (timeout > 0 && System.currentTimeMillis() - time > timeout) {
return false
}
}
return true
}
inline fun orientationSensorEventListener(
crossinline callback: (Float, Float, Float) -> Unit
) = object: SensorEventListener {
private var accelerometerData: FloatArray? = null
private var geomagneticData: FloatArray? = null
override fun onSensorChanged(event: SensorEvent) {
if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) accelerometerData = event.values
if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) geomagneticData = event.values
if (accelerometerData != null && geomagneticData != null) {
val R = FloatArray(9)
val success = SensorManager.getRotationMatrix(
R,
FloatArray(9),
accelerometerData,
geomagneticData
)
if (success) {
val orientationData = FloatArray(3)
SensorManager.getOrientation(R, orientationData)
callback(
orientationData[0],
orientationData[1],
orientationData[2]
)
}
}
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
inline fun SensorEventListener(crossinline callback: (SensorEvent) -> Unit) = object: SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
callback(event)
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
inline val Context.isScreenLocked get() = (getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager).isKeyguardLocked
@Suppress("MemberVisibilityCanBePrivate", "unused")
class NotificationIdManager(vararg reservedIds: Int) {
private val reserved = reservedIds.toMutableList()
fun reserve(id: Int) {
reserved.add(id)
}
fun release(id: Int) {
reserved.remove(id)
}
fun get(): Int {
while (true) {
val random = Random.nextInt(0..Int.MAX_VALUE)
if (!reserved.contains(random)) {
return random
}
}
}
fun getAndReserve(): Int {
val id = get()
reserve(id)
return id
}
}
@Composable
fun PaddingValues.copy(
start: Dp? = null,
end: Dp? = null,
top: Dp? = null,
bottom: Dp? = null
) = PaddingValues(
start = start ?: calculateStartPadding(LocalLayoutDirection.current),
end = end ?: calculateEndPadding(LocalLayoutDirection.current),
top = top ?: calculateTopPadding(),
bottom = bottom ?: calculateBottomPadding()
)
@Suppress("unused")
@Composable
fun PaddingValues.copy(
horizontal: Dp? = null,
vertical: Dp? = null
) = copy(
start = horizontal,
end = horizontal,
top = vertical,
bottom = vertical
)
inline fun InputStream.test() =
try {
read()
close()
true
} catch (_: IOException) {
false
}
inline fun ContentResolver.test(item: Uri) =
try {
openInputStream(item)!!.test()
} catch (_: Exception) {
false
}
inline fun <reified T: Parcelable> Intent.getParcelableExtraAs(key: String): T {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelableExtra(key, T::class.java)
} else {
@Suppress("DEPRECATION")
getParcelableExtra(key)
}!!
}
inline fun <reified T: Serializable> Intent.getSerializableExtraAs(key: String): T {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getSerializableExtra(key, T::class.java)
} else {
@Suppress("DEPRECATION")
getSerializableExtra(key) as T
}!!
}
fun ActivityInfo.contentEquals(other: ActivityInfo): Boolean {
return (
(
if (Build.VERSION.SDK_INT >= 26)
colorMode == other.colorMode
else true
) &&
(
if (Build.VERSION.SDK_INT >= 34)
requiredDisplayCategory == other.requiredDisplayCategory
else true
) &&
theme == other.theme &&
launchMode == other.launchMode &&
documentLaunchMode == other.documentLaunchMode &&
permission == other.permission &&
taskAffinity == other.taskAffinity &&
targetActivity == other.targetActivity &&
flags == other.flags &&
screenOrientation == other.screenOrientation &&
configChanges == other.configChanges &&
softInputMode == other.softInputMode &&
uiOptions == other.uiOptions &&
parentActivityName == other.parentActivityName &&
maxRecents == other.maxRecents &&
windowLayout == other.windowLayout
)
}
val ActivityInfo.launchIntent get() = Intent().apply {
component = ComponentName(packageName!!, name)
flags = FLAG_ACTIVITY_NEW_TASK
}
@RequiresPermission(Manifest.permission.QUERY_ALL_PACKAGES)
fun ActivityInfo.isLauncher(context: Context): Boolean {
with (context) {
@Suppress("ExplicitThis")
val intent = launchIntent.apply {
action = ACTION_MAIN
addCategory(CATEGORY_LAUNCHER)
component = ComponentName(this@isLauncher.packageName!!, name)
flags = FLAG_ACTIVITY_NEW_TASK
}
val infos = packageManager.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER)
for (info in infos) {
val filter = info.filter
if (
info.activityInfo.contentEquals(this@isLauncher) &&
filter != null &&
filter.hasAction(ACTION_MAIN) &&
filter.hasCategory(CATEGORY_LAUNCHER)
) {
return true
}
}
return false
}
}
@Suppress("unused", "RedundantSuppression")
operator fun <T> WeakReference<T>.getValue(thisRef: Any?, property: KProperty<*>) = get()!!
@Suppress("unused")
inline fun runOrLog(
tag: String,
message: String = "An error occurred:",
crossinline block: () -> Unit
) {
try {
block()
} catch (e: Exception) {
Log.e(tag, message, e)
}
}
@Suppress("unused")
typealias WidthSizeClass = WindowWidthSizeClass
@Suppress("unused")
typealias HeightSizeClass = WindowHeightSizeClass
@Suppress("unused")
typealias SizeClass = WindowSizeClass
@RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
inline fun Context.notifyIfAllowed(
id: Int,
notification: Notification
) {
with (NotificationManagerCompat.from(this)) {
if (
checkSelfPermission(
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
return@with
}
notify(id, notification)
}
}
@Suppress("DEPRECATION")
fun BaseBundle.toMap(): Map<String, Any> {
val map = mutableMapOf<String, Any>()
for (key in keySet()) {
map[key] = get(key)!!
}
return map
}
fun Bundle.toPersistableBundle(): PersistableBundle {
val map = toMap()
val bundle = PersistableBundle()
for ((key, value) in map) {
when (value) {
is Int -> bundle.putInt(key, value)
is Long -> bundle.putLong(key, value)
is Double -> bundle.putDouble(key, value)
is String -> bundle.putString(key, value)
is IntArray -> bundle.putIntArray(key, value)
is LongArray -> bundle.putLongArray(key, value)
is DoubleArray -> bundle.putDoubleArray(key, value)
is PersistableBundle -> bundle.putPersistableBundle(key, value)
is Boolean -> bundle.putBoolean(key, value)
is BooleanArray -> bundle.putBooleanArray(key, value)
is Array<*> -> {
if (value.isArrayOf<String>()) {
@Suppress("UNCHECKED_CAST")
bundle.putStringArray(key, value as Array<String>)
}
}
}
}
return bundle
}
inline fun String.decodeWindows1251() = String(
toByteArray(Charsets.ISO_8859_1),
Charsets.UTF_8
)
@get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S)
actual val materialYouAvailable get() = Build.VERSION.SDK_INT >= 31
@@ -0,0 +1,17 @@
package com.pr0gramm3r101.utils
import android.content.Context
import java.lang.ref.WeakReference
actual object UtilsLibrary {
private var init = false
private lateinit var _context: WeakReference<Context>
val context get() = _context.get()!!
actual fun init(vararg args: Any) {
if (!init) {
_context = WeakReference(args[0] as Context)
init = true
}
}
}
@@ -0,0 +1,13 @@
package com.pr0gramm3r101.utils.crypto
import android.util.Base64 as AndroidBase64
actual object Base64 {
actual fun encode(bytes: ByteArray): String {
return AndroidBase64.encodeToString(bytes, AndroidBase64.NO_WRAP)
}
actual fun decode(base64: String): ByteArray {
return AndroidBase64.decode(base64, AndroidBase64.NO_WRAP)
}
}
@@ -0,0 +1,15 @@
package com.pr0gramm3r101.utils.crypto
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
actual object Hmac {
actual suspend fun hmacSha256(key: ByteArray, data: ByteArray): ByteArray = withContext(Dispatchers.Default) {
val mac = Mac.getInstance("HmacSHA256")
val secretKey = SecretKeySpec(key, "HmacSHA256")
mac.init(secretKey)
mac.doFinal(data)
}
}
@@ -0,0 +1,114 @@
package com.pr0gramm3r101.utils.settings
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.floatPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.stringSetPreferencesKey
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
class AndroidSettings(): Settings {
private val dataStore: DataStore<Preferences>
get() = DataStoreSingleton.dataStore
override fun putString(key: String, value: String) {
runBlocking {
dataStore.edit { preferences ->
preferences[stringPreferencesKey(key)] = value
}
}
}
override fun getString(key: String, default: String) = runBlocking {
dataStore.data.map { preferences ->
preferences[stringPreferencesKey(key)] ?: default
}.first()
}
override fun putInt(key: String, value: Int) = runBlocking {
dataStore.edit { preferences ->
preferences[intPreferencesKey(key)] = value
}
Unit
}
override fun getInt(key: String, default: Int) = runBlocking {
dataStore.data.map { preferences ->
preferences[intPreferencesKey(key)] ?: default
}.first()
}
override fun putLong(key: String, value: Long) = runBlocking {
dataStore.edit { preferences ->
preferences[longPreferencesKey(key)] = value
}
Unit
}
override fun getLong(key: String, default: Long) = runBlocking {
dataStore.data.map { preferences ->
preferences[longPreferencesKey(key)] ?: default
}.first()
}
override fun putFloat(key: String, value: Float) = runBlocking {
dataStore.edit { preferences ->
preferences[floatPreferencesKey(key)] = value
}
Unit
}
override fun getFloat(key: String, default: Float) = runBlocking {
dataStore.data.map { preferences ->
preferences[floatPreferencesKey(key)] ?: default
}.first()
}
override fun putBoolean(key: String, value: Boolean) = runBlocking {
dataStore.edit { preferences ->
preferences[booleanPreferencesKey(key)] = value
}
Unit
}
override fun getBoolean(key: String, default: Boolean) = runBlocking {
dataStore.data.map { preferences ->
preferences[booleanPreferencesKey(key)] ?: default
}.first()
}
override fun putStringSet(key: String, value: Set<String>) = runBlocking {
dataStore.edit { preferences ->
preferences[stringSetPreferencesKey(key)] = value
}
Unit
}
override fun getStringSet(key: String, default: Set<String>) = runBlocking {
dataStore.data.map { preferences ->
preferences[stringSetPreferencesKey(key)] ?: default
}.first()
}
override fun putStringList(key: String, value: List<String>) = putStringSet(key, value.toSet())
override fun getStringList(key: String, default: List<String>) = getStringSet(key, default.toSet()).toList()
override fun remove(key: String) = runBlocking {
dataStore.edit { preferences ->
preferences.remove(stringPreferencesKey(key))
preferences.remove(intPreferencesKey(key))
preferences.remove(longPreferencesKey(key))
preferences.remove(floatPreferencesKey(key))
preferences.remove(booleanPreferencesKey(key))
preferences.remove(stringSetPreferencesKey(key))
}
Unit
}
}
@@ -0,0 +1,128 @@
package com.pr0gramm3r101.utils.settings
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.floatPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.stringSetPreferencesKey
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
class AndroidAsyncSettings : AsyncSettings {
private val dataStore: DataStore<Preferences>
get() = DataStoreSingleton.dataStore
override suspend fun putString(key: String, value: String) {
dataStore.edit { preferences ->
preferences[stringPreferencesKey(key)] = value
}
}
override suspend fun getString(key: String, default: String): String {
return dataStore.data.map { preferences ->
preferences[stringPreferencesKey(key)] ?: default
}.first()
}
override suspend fun putInt(key: String, value: Int) {
dataStore.edit { preferences ->
preferences[intPreferencesKey(key)] = value
}
}
override suspend fun getInt(key: String, default: Int): Int {
return dataStore.data.map { preferences ->
preferences[intPreferencesKey(key)] ?: default
}.first()
}
override suspend fun putLong(key: String, value: Long) {
dataStore.edit { preferences ->
preferences[longPreferencesKey(key)] = value
}
}
override suspend fun getLong(key: String, default: Long): Long {
return dataStore.data.map { preferences ->
preferences[longPreferencesKey(key)] ?: default
}.first()
}
override suspend fun putFloat(key: String, value: Float) {
dataStore.edit { preferences ->
preferences[floatPreferencesKey(key)] = value
}
}
override suspend fun getFloat(key: String, default: Float): Float {
return dataStore.data.map { preferences ->
preferences[floatPreferencesKey(key)] ?: default
}.first()
}
override suspend fun putBoolean(key: String, value: Boolean) {
dataStore.edit { preferences ->
preferences[booleanPreferencesKey(key)] = value
}
}
override suspend fun getBoolean(key: String, default: Boolean): Boolean {
return dataStore.data.map { preferences ->
preferences[booleanPreferencesKey(key)] ?: default
}.first()
}
override suspend fun putStringSet(key: String, value: Set<String>) {
dataStore.edit { preferences ->
preferences[stringSetPreferencesKey(key)] = value
}
}
override suspend fun getStringSet(key: String, default: Set<String>): Set<String> {
return dataStore.data.map { preferences ->
preferences[stringSetPreferencesKey(key)] ?: default
}.first()
}
override suspend fun putStringList(key: String, value: List<String>) {
putStringSet(key, value.toSet())
}
override suspend fun getStringList(key: String, default: List<String>): List<String> {
return getStringSet(key, default.toSet()).toList()
}
override suspend fun remove(key: String) {
dataStore.edit { preferences ->
preferences.remove(stringPreferencesKey(key))
preferences.remove(intPreferencesKey(key))
preferences.remove(longPreferencesKey(key))
preferences.remove(floatPreferencesKey(key))
preferences.remove(booleanPreferencesKey(key))
preferences.remove(stringSetPreferencesKey(key))
}
}
override suspend fun contains(key: String): Boolean {
return dataStore.data.map { preferences ->
preferences.contains(stringPreferencesKey(key)) ||
preferences.contains(intPreferencesKey(key)) ||
preferences.contains(longPreferencesKey(key)) ||
preferences.contains(floatPreferencesKey(key)) ||
preferences.contains(booleanPreferencesKey(key)) ||
preferences.contains(stringSetPreferencesKey(key))
}.first()
}
override suspend fun clear() {
dataStore.edit { preferences ->
preferences.clear()
}
}
}
actual val asyncSettings: AsyncSettings = AndroidAsyncSettings()
@@ -0,0 +1,17 @@
package com.pr0gramm3r101.utils.settings
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import com.pr0gramm3r101.utils.UtilsLibrary
/**
* Singleton DataStore instance to avoid multiple DataStore instances for the same file
*/
object DataStoreSingleton {
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
val dataStore: DataStore<Preferences>
get() = UtilsLibrary.context.dataStore
}
@@ -0,0 +1,3 @@
package com.pr0gramm3r101.utils.settings
actual val settings: Settings get() = AndroidSettings()