diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 35cb41f..4aa5fe0 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,10 +1,11 @@
-import java.util.Properties
import org.jetbrains.kotlin.config.KotlinCompilerVersion
+import java.util.Properties
plugins {
id("com.android.application")
kotlin("android")
kotlin("kapt")
+ id("com.google.devtools.ksp")
id("kotlin-parcelize")
}
@@ -15,9 +16,9 @@ android {
defaultConfig {
applicationId = AppConfig.appId
minSdk = AppConfig.minSdkVersion
- targetSdk = AppConfig.targetSdlVersion
- versionCode = 15
- versionName = "2.2.1"
+ targetSdk = AppConfig.targetSdkVersion
+ versionCode = 16
+ versionName = "2.2.2"
vectorDrawables.useSupportLibrary = true
resourceConfigurations.addAll(listOf("en", "de", "pt-rBR"))
javaCompileOptions {
@@ -62,8 +63,8 @@ android {
}
buildFeatures {
- android.buildFeatures.viewBinding = true
- android.buildFeatures.dataBinding = true
+ viewBinding = true
+ dataBinding = true
compose = true
}
@@ -73,7 +74,7 @@ android {
getByName("androidTest").assets.srcDir("$projectDir/schemas")
}
- packagingOptions {
+ packaging {
resources.excludes.addAll(
arrayOf(
"/META-INF/**",
@@ -88,12 +89,12 @@ android {
}
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = "1.8"
+ kotlin {
+ jvmToolchain(17)
}
composeOptions {
@@ -101,10 +102,6 @@ android {
}
}
-kapt {
- correctErrorTypes = true
-}
-
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation(kotlin("stdlib-jdk8", KotlinCompilerVersion.VERSION))
@@ -123,7 +120,8 @@ dependencies {
implementation(AndroidX.preference)
implementation(AndroidX.room)
implementation(AndroidX.roomRuntime)
- kapt(AndroidX.roomCompiler)
+ implementation(AndroidX.workManager)
+ ksp(AndroidX.roomCompiler)
implementation(Google.gson)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5a45d1a..e683bad 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -8,6 +8,10 @@
+
+
+
+
+ android:exported="true">
@@ -90,19 +94,12 @@
-
-
@@ -113,7 +110,7 @@
diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/Extensions.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/Extensions.kt
index 5b02c4d..4f82a5e 100644
--- a/app/src/main/kotlin/com/androidvip/sysctlgui/Extensions.kt
+++ b/app/src/main/kotlin/com/androidvip/sysctlgui/Extensions.kt
@@ -16,17 +16,17 @@ import com.androidvip.sysctlgui.receivers.TaskerReceiver
import com.google.android.material.color.ColorRoles
import com.google.android.material.color.MaterialColors
import com.google.android.material.snackbar.Snackbar
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
import java.io.InputStream
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
fun View.goAway() { this.visibility = View.GONE }
fun View.hide() { this.visibility = View.INVISIBLE }
fun View.show() { this.visibility = View.VISIBLE }
-fun View.getColorRoles(@AttrRes colorAttrRes: Int = R.attr.colorPrimary): ColorRoles {
+fun View.getColorRoles(@AttrRes colorAttrRes: Int = androidx.appcompat.R.attr.colorPrimary): ColorRoles {
val color = MaterialColors.getColor(this, colorAttrRes)
return MaterialColors.getColorRoles(context, color)
}
diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/receivers/BootReceiver.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/receivers/BootReceiver.kt
index 8e22962..2897d2f 100644
--- a/app/src/main/kotlin/com/androidvip/sysctlgui/receivers/BootReceiver.kt
+++ b/app/src/main/kotlin/com/androidvip/sysctlgui/receivers/BootReceiver.kt
@@ -3,14 +3,12 @@ package com.androidvip.sysctlgui.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import com.androidvip.sysctlgui.services.StartUpService
+import com.androidvip.sysctlgui.work.StartUpWorker
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {
- context?.let {
- StartUpService.start(context)
- }
+ context?.let { StartUpWorker.enqueue(context.applicationContext) }
}
}
}
diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/receivers/TaskerReceiver.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/receivers/TaskerReceiver.kt
index e01063d..e6a3702 100644
--- a/app/src/main/kotlin/com/androidvip/sysctlgui/receivers/TaskerReceiver.kt
+++ b/app/src/main/kotlin/com/androidvip/sysctlgui/receivers/TaskerReceiver.kt
@@ -5,9 +5,9 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
-import com.androidvip.sysctlgui.utils.Consts
import com.androidvip.sysctlgui.isValidTaskerBundle
-import com.androidvip.sysctlgui.services.TaskerService
+import com.androidvip.sysctlgui.utils.Consts
+import com.androidvip.sysctlgui.work.TaskerWorker
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
@@ -19,11 +19,7 @@ class TaskerReceiver : BroadcastReceiver() {
val bundle: Bundle? = intent.getBundleExtra(EXTRA_BUNDLE)
if (bundle.isValidTaskerBundle()) {
val taskerList = bundle.getInt(BUNDLE_EXTRA_LIST_NUMBER, Consts.LIST_NUMBER_INVALID)
- val serviceIntent = Intent(context, TaskerService::class.java).apply {
- putExtra(BUNDLE_EXTRA_LIST_NUMBER, taskerList)
- }
-
- context.startService(serviceIntent)
+ TaskerWorker.enqueue(context.applicationContext, taskerList)
} else {
Log.w(TAG, "Invalid tasker bundle: $bundle")
}
diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/services/StartUpService.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/services/StartUpService.kt
deleted file mode 100644
index 24fb631..0000000
--- a/app/src/main/kotlin/com/androidvip/sysctlgui/services/StartUpService.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.androidvip.sysctlgui.services
-
-import android.app.Notification
-import android.app.job.JobParameters
-import android.app.job.JobService
-import android.content.Context
-import android.content.Intent
-import android.os.Build
-import com.androidvip.sysctlgui.services.base.BaseStartUpService
-import java.lang.ref.WeakReference
-
-class StartUpService : JobService(), BaseStartUpService.ServiceHandler {
-
- override fun onStartJob(params: JobParameters?): Boolean {
- return true
- }
-
- override fun onStopJob(params: JobParameters?): Boolean {
- return true
- }
-
- override fun onStart(intent: Intent?, startId: Int) {
- BaseStartUpService(WeakReference(this), this).onStart()
- }
-
- override fun onStartForeground(id: Int, notification: Notification) {
- this.startForeground(id, notification)
- }
-
- override fun onStopForeground(removeNotification: Boolean) {
- this.stopForeground(true)
- }
-
- companion object {
-
- fun start(context: Context) {
- val intent = Intent(context, StartUpService::class.java)
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- context.startForegroundService(intent)
- } else {
- context.startService(intent)
- }
- }
- }
-}
diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/services/TaskerService.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/services/TaskerService.kt
deleted file mode 100644
index 7534e19..0000000
--- a/app/src/main/kotlin/com/androidvip/sysctlgui/services/TaskerService.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.androidvip.sysctlgui.services
-
-import android.app.Service
-import android.content.Intent
-import android.os.IBinder
-import android.widget.Toast
-import com.androidvip.sysctlgui.R
-import com.androidvip.sysctlgui.utils.Consts
-import com.androidvip.sysctlgui.domain.repository.AppPrefs
-import com.androidvip.sysctlgui.domain.usecase.ApplyParamsUseCase
-import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase
-import com.androidvip.sysctlgui.receivers.TaskerReceiver
-import com.androidvip.sysctlgui.toast
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancelChildren
-import kotlinx.coroutines.launch
-import org.koin.android.ext.android.inject
-import kotlin.contracts.ExperimentalContracts
-import kotlin.coroutines.CoroutineContext
-
-@ExperimentalContracts
-class TaskerService : Service(), CoroutineScope {
- override val coroutineContext: CoroutineContext = Dispatchers.Main + SupervisorJob()
- private val appPrefs: AppPrefs by inject()
- private val getUserParamsUseCase: GetUserParamsUseCase by inject()
- private val applyParamsUseCase: ApplyParamsUseCase by inject()
-
- override fun onBind(intent: Intent): IBinder? {
- return null
- }
-
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- if (intent == null) return START_NOT_STICKY
-
- val taskerList = intent.getIntExtra(
- TaskerReceiver.BUNDLE_EXTRA_LIST_NUMBER,
- Consts.LIST_NUMBER_INVALID
- )
-
- launch {
- applyParams(taskerList)
- if (appPrefs.showTaskerToast) {
- toast(getString(R.string.tasker_toast, taskerList), Toast.LENGTH_LONG)
- }
- }
-
- return START_NOT_STICKY
- }
-
- override fun onDestroy() {
- coroutineContext[Job]?.cancelChildren()
- super.onDestroy()
- }
-
- private suspend fun applyParams(listNumber: Int) {
- val params = getUserParamsUseCase()
- when (listNumber) {
- Consts.LIST_NUMBER_PRIMARY_TASKER,
- Consts.LIST_NUMBER_SECONDARY_TASKER -> params.filter { it.taskerParam }
- Consts.LIST_NUMBER_FAVORITES -> params.filter { it.favorite }
- Consts.LIST_NUMBER_APPLY_ON_BOOT -> params
-
- else -> emptyList()
- }.forEach {
- applyParamsUseCase(it)
- }
- }
-}
diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/services/base/BaseStartUpService.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/services/base/BaseStartUpService.kt
deleted file mode 100644
index 053bce3..0000000
--- a/app/src/main/kotlin/com/androidvip/sysctlgui/services/base/BaseStartUpService.kt
+++ /dev/null
@@ -1,180 +0,0 @@
-package com.androidvip.sysctlgui.services.base
-
-import android.Manifest
-import android.app.Notification
-import android.app.NotificationChannel
-import android.app.NotificationManager
-import android.content.Context
-import android.content.pm.PackageManager
-import android.os.Build
-import androidx.core.app.ActivityCompat
-import androidx.core.app.NotificationCompat
-import androidx.core.app.NotificationManagerCompat
-import com.androidvip.sysctlgui.R
-import com.androidvip.sysctlgui.data.utils.RootUtils
-import com.androidvip.sysctlgui.domain.repository.AppPrefs
-import com.androidvip.sysctlgui.domain.usecase.ApplyParamsUseCase
-import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase
-import com.topjohnwu.superuser.Shell
-import java.lang.ref.WeakReference
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancelChildren
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import org.koin.core.component.KoinComponent
-import org.koin.core.component.inject
-
-class BaseStartUpService(
- private var weakContext: WeakReference,
- private var handler: ServiceHandler?,
- private val dispatcher: CoroutineDispatcher = Dispatchers.IO
-) : CoroutineScope, KoinComponent {
- override val coroutineContext: CoroutineContext = Dispatchers.Main + SupervisorJob()
-
- /**
- * important: implement method to check if the device keep crashing on boot and disable start up
- * maybe add a counter to prefs and if the value is > 3 disable
- */
- private val appPrefs: AppPrefs by inject()
- private val rootUtils: RootUtils by inject()
- private val getUserParamsUseCase: GetUserParamsUseCase by inject()
- private val applyParamsUseCase: ApplyParamsUseCase by inject()
-
- fun onStart() {
- weakContext.get()?.let { context ->
- // call the .conf file and make an notification for android >= O
- showNotificationAndThen {
- launch {
- if (checkRequirements()) {
- applyConfig()
- }
-
- if (handler != null) {
- handler?.onStopForeground(true)
- } else {
- NotificationManagerCompat.from(context).cancel(SERVICE_ID)
- }
-
- onCleanUp()
- }
- }
- }
- }
-
- private inline fun showNotificationAndThen(crossinline onShow: () -> Unit) {
- val context = weakContext.get() ?: return
- val resources = context.resources
- val builder: NotificationCompat.Builder = NotificationCompat.Builder(
- context,
- NOTIFICATION_ID
- )
- .setSmallIcon(R.drawable.app_icon_foreground)
- .setContentTitle(resources.getString(R.string.notification_start_up_title))
- .setPriority(NotificationCompat.PRIORITY_DEFAULT)
- .setOnlyAlertOnce(true)
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val notificationManager: NotificationManager =
- context.getSystemService(NotificationManager::class.java)
-
- NotificationChannel(
- NOTIFICATION_ID,
- resources.getString(R.string.notification_start_up_channel_name),
- NotificationManager.IMPORTANCE_DEFAULT
- ).apply {
- description = resources.getString(
- R.string.notification_start_up_channel_description
- )
- notificationManager.createNotificationChannel(this)
- }
- }
-
- val startupDelay = appPrefs.startUpDelay
-
- if (startupDelay > 0) {
- builder.setContentTitle(
- context.getString(
- R.string.notification_start_up_description_delay,
- startupDelay
- )
- )
- builder.setProgress(startupDelay, 0, true)
-
- launch {
- NotificationManagerCompat.from(context).apply {
- var delayCount = 0
- for (i in startupDelay downTo 0) {
- if (i == 0) {
- builder.setProgress(0, 0, true)
- builder.setContentTitle(
- context.getString(R.string.notification_start_up_title)
- )
- builder.setContentText(
- context.getString(R.string.notification_start_up_description)
- )
-
- if (ActivityCompat.checkSelfPermission(
- context,
- Manifest.permission.POST_NOTIFICATIONS
- ) == PackageManager.PERMISSION_GRANTED ||
- Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU
- ) {
- notify(SERVICE_ID, builder.build())
- }
- handler?.onStartForeground(SERVICE_ID, builder.build())
- onShow()
- } else {
- builder.setContentTitle(
- context.getString(
- R.string.notification_start_up_description_delay,
- i
- )
- )
- builder.setProgress(startupDelay, delayCount, false)
- notify(SERVICE_ID, builder.build())
- delay(1100)
- delayCount++
- }
- }
- }
- }
- } else {
- builder.setContentText(context.getString(R.string.notification_start_up_description))
- handler?.onStartForeground(SERVICE_ID, builder.build())
- onShow()
- }
- }
-
- private suspend fun applyConfig() {
- getUserParamsUseCase().forEach {
- applyParamsUseCase(it)
- }
- }
-
- private suspend fun checkRequirements() = withContext(dispatcher) {
- appPrefs.runOnStartUp && Shell.rootAccess()
- }
-
- private fun onCleanUp() {
- // avoid memory leaks
- this.handler = null
- coroutineContext[Job]?.cancelChildren()
- rootUtils.finishProcess()
- }
-
- interface ServiceHandler {
- fun onStartForeground(id: Int, notification: Notification)
- fun onStopForeground(removeNotification: Boolean)
- }
-
- companion object {
- private const val SERVICE_ID: Int = 2
- private const val NOTIFICATION_ID: String = "2"
- }
-}
diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseAppCompatActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseAppCompatActivity.kt
index 31f201a..12896ea 100644
--- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseAppCompatActivity.kt
+++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseAppCompatActivity.kt
@@ -3,7 +3,7 @@ package com.androidvip.sysctlgui.ui.base
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
-import com.androidvip.sysctlgui.R
+import com.androidvip.sysctlgui.design.DesignStyles
import com.androidvip.sysctlgui.domain.repository.AppPrefs
import org.koin.android.ext.android.inject
@@ -19,7 +19,7 @@ abstract class BaseAppCompatActivity : AppCompatActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false)
if (prefs.forceDark) {
- setTheme(R.style.AppTheme_ForceDark)
+ setTheme(DesignStyles.AppTheme_ForceDark)
}
}
}
diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsViewModel.kt
index 102116a..9caec46 100644
--- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsViewModel.kt
+++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsViewModel.kt
@@ -138,10 +138,9 @@ class ExportOptionsViewModel(
context: Context
): Result = withContext(ioDispatcher) {
return@withContext runCatching {
- val descriptor = context.contentResolver.openFileDescriptor(target, "w")
- ?: throw IOException()
-
- exportParamsUseCase(descriptor.fileDescriptor)
+ context.contentResolver.openFileDescriptor(target, "w").use {
+ exportParamsUseCase(it!!.fileDescriptor)
+ }
}
}
@@ -150,10 +149,9 @@ class ExportOptionsViewModel(
context: Context
): Result = withContext(ioDispatcher) {
return@withContext runCatching {
- val descriptor = context.contentResolver.openFileDescriptor(target, "w")
- ?: throw IOException()
-
- backupParamsUseCase(descriptor.fileDescriptor)
+ context.contentResolver.openFileDescriptor(target, "w").use {
+ backupParamsUseCase(it!!.fileDescriptor)
+ }
}
}
diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/KernelParamBrowseFragment.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/KernelParamBrowseFragment.kt
index 2404477..02f6406 100644
--- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/KernelParamBrowseFragment.kt
+++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/KernelParamBrowseFragment.kt
@@ -23,7 +23,7 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
-import androidx.compose.material3.Divider
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
@@ -38,6 +38,8 @@ import androidx.navigation.fragment.findNavController
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.androidvip.sysctlgui.R
import com.androidvip.sysctlgui.data.models.KernelParam
+import com.androidvip.sysctlgui.design.DesignIds
+import com.androidvip.sysctlgui.design.DesignLayouts
import com.androidvip.sysctlgui.getColorRoles
import com.androidvip.sysctlgui.goAway
import com.androidvip.sysctlgui.show
@@ -185,14 +187,14 @@ class KernelParamBrowseFragment : BaseSearchFragment(), OnParamItemClickedListen
val dialog = Dialog(requireContext()).apply {
requestWindowFeature(Window.FEATURE_NO_TITLE)
- setContentView(R.layout.dialog_web)
+ setContentView(DesignLayouts.dialog_web)
setCancelable(true)
}
- val progressBar: ProgressBar = dialog.findViewById(R.id.webDialogProgress)
- val swipeLayout: SwipeRefreshLayout = dialog.findViewById(R.id.webDialogSwipeLayout)
+ val progressBar: ProgressBar = dialog.findViewById(DesignIds.webDialogProgress)
+ val swipeLayout: SwipeRefreshLayout = dialog.findViewById(DesignIds.webDialogSwipeLayout)
- val webView = dialog.findViewById(R.id.webDialogWebView).apply {
+ val webView = dialog.findViewById(DesignIds.webDialogWebView).apply {
val colorRoles = getColorRoles()
settings.apply {
javaScriptEnabled = true
@@ -264,7 +266,10 @@ class KernelParamBrowseFragment : BaseSearchFragment(), OnParamItemClickedListen
paramFile = File(param.path)
)
if (index < params.lastIndex) {
- Divider(color = MaterialTheme.colorScheme.outlineVariant, thickness = 1.dp)
+ HorizontalDivider(
+ thickness = 1.dp,
+ color = MaterialTheme.colorScheme.outlineVariant
+ )
}
}
}
diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt
index 574eb2a..44e5713 100644
--- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt
+++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt
@@ -1,5 +1,6 @@
package com.androidvip.sysctlgui.ui.params.edit
+import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
@@ -75,6 +76,7 @@ class EditParamViewModel(
viewModelScope.launch {
runCatching {
applyParams(param)
+ updateUserParam(param)
}.onFailure {
val messageRes = when (it) {
is ApplyValueException -> R.string.apply_value_error
@@ -115,6 +117,7 @@ class EditParamViewModel(
return KeyboardType.Text
}
+ @SuppressLint("DiscouragedApi")
private fun findParamInfo(param: KernelParam, context: Context): String? = with(context) {
val paramName = param.shortName
val resId = resources.getIdentifier(
@@ -185,7 +188,6 @@ class EditParamViewModel(
PackageManager.PackageInfoFlags.of(0L)
)
} else {
- @Suppress("DEPRECATION")
packageManager.getPackageInfo(TASKER_PACKAGE_NAME, 0)
}
true
diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsScreen.kt
index 85b5377..5f5abf0 100644
--- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsScreen.kt
+++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsScreen.kt
@@ -11,22 +11,21 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.ArrowBack
+import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Search
-import androidx.compose.material3.DismissDirection
-import androidx.compose.material3.DismissValue
-import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SearchBar
-import androidx.compose.material3.SwipeToDismiss
+import androidx.compose.material3.SwipeToDismissBox
+import androidx.compose.material3.SwipeToDismissBoxValue
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
-import androidx.compose.material3.rememberDismissState
+import androidx.compose.material3.rememberSwipeToDismissBoxState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -75,7 +74,7 @@ fun UserParamsScreen(
navigationIcon = {
IconButton(onClick = onBackPressed) {
Icon(
- imageVector = Icons.Outlined.ArrowBack,
+ imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
contentDescription = stringResource(id = R.string.restore_param),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
@@ -127,21 +126,23 @@ private fun SwipeToDismissContent(
param: KernelParam
) {
val currentParam by rememberUpdatedState(newValue = param)
- val dismissState = rememberDismissState(
+ val dismissState = rememberSwipeToDismissBoxState(
confirmValueChange = {
- return@rememberDismissState if (it == DismissValue.DismissedToStart) {
- onDelete(currentParam)
- true
- } else {
- false
+ fun getResultFromValueChange(): Boolean {
+ if (it == SwipeToDismissBoxValue.EndToStart) {
+ onDelete(currentParam)
+ return true
+ }
+ return false
}
+ getResultFromValueChange()
}
)
- SwipeToDismiss(
+ SwipeToDismissBox(
state = dismissState,
- directions = setOf(DismissDirection.EndToStart),
- background = {
+ enableDismissFromEndToStart = true,
+ backgroundContent = {
Box(
modifier = Modifier
.fillMaxSize()
@@ -155,20 +156,19 @@ private fun SwipeToDismissContent(
tint = MaterialTheme.colorScheme.onError
)
}
- },
- dismissContent = {
- Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
- ParamItem(
- onParamClick = onParamClick,
- param = param
- )
- Divider(
- color = MaterialTheme.colorScheme.outlineVariant,
- thickness = 1.dp
- )
- }
}
- )
+ ) {
+ Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
+ ParamItem(
+ onParamClick = onParamClick,
+ param = param
+ )
+ HorizontalDivider(
+ thickness = 1.dp,
+ color = MaterialTheme.colorScheme.outlineVariant
+ )
+ }
+ }
}
@OptIn(ExperimentalMaterial3Api::class)
diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewModel.kt
index 6754e28..8ea0e27 100644
--- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewModel.kt
+++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewModel.kt
@@ -61,6 +61,7 @@ class UserParamsViewModel(
fun setBaseFilterPredicate(predicate: ParamFilterPredicate) {
baseFilterPredicate = predicate
+ currentFilterPredicate = baseFilterPredicate
}
private fun delete(kernelParam: KernelParam) {
diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/work/StartUpWorker.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/work/StartUpWorker.kt
new file mode 100644
index 0000000..708f1d5
--- /dev/null
+++ b/app/src/main/kotlin/com/androidvip/sysctlgui/work/StartUpWorker.kt
@@ -0,0 +1,169 @@
+package com.androidvip.sysctlgui.work
+
+import android.Manifest
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import androidx.core.app.ActivityCompat
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import androidx.work.CoroutineWorker
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
+import androidx.work.WorkRequest
+import androidx.work.WorkerParameters
+import com.androidvip.sysctlgui.R
+import com.androidvip.sysctlgui.data.utils.RootUtils
+import com.androidvip.sysctlgui.domain.repository.AppPrefs
+import com.androidvip.sysctlgui.domain.usecase.ApplyParamsUseCase
+import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase
+import com.topjohnwu.superuser.Shell
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.withContext
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
+import kotlin.time.Duration.Companion.seconds
+
+class StartUpWorker(
+ private val context: Context,
+ workerParams: WorkerParameters
+) : CoroutineWorker(context, workerParams), KoinComponent {
+ private val mainContext = Dispatchers.Main + SupervisorJob()
+ private val workerContext = Dispatchers.Default
+ private val appPrefs: AppPrefs by inject()
+ private val rootUtils: RootUtils by inject()
+ private val getUserParamsUseCase: GetUserParamsUseCase by inject()
+ private val applyParamsUseCase: ApplyParamsUseCase by inject()
+
+ private val notificationManager: NotificationManagerCompat
+ get() = NotificationManagerCompat.from(context)
+
+ override suspend fun doWork(): Result {
+ withContext(mainContext) {
+ showNotificationAndThen { builder ->
+ if (checkRequirements()) {
+ applyConfig(builder)
+ }
+
+ delay(1.seconds)
+ notificationManager.cancel(SERVICE_ID)
+
+ withContext(workerContext) {
+ rootUtils.finishProcess()
+ }
+ }
+ }
+
+
+ return Result.success()
+ }
+
+ private suspend fun applyConfig(builder: NotificationCompat.Builder) {
+ getUserParamsUseCase().forEach {
+ builder.setContentText(it.toString())
+ notifyIfPossible(builder)
+ delay(250L)
+ applyParamsUseCase(it)
+ }
+ }
+
+ private suspend fun checkRequirements() = withContext(workerContext) {
+ appPrefs.runOnStartUp && Shell.rootAccess()
+ }
+
+ private suspend inline fun showNotificationAndThen(
+ crossinline onShow: suspend (NotificationCompat.Builder) -> Unit
+ ) {
+ val resources = context.resources
+ val builder: NotificationCompat.Builder = NotificationCompat.Builder(
+ context,
+ NOTIFICATION_ID
+ )
+ .setSmallIcon(R.drawable.app_icon_foreground)
+ .setContentTitle(resources.getString(R.string.notification_start_up_title))
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setOnlyAlertOnce(true)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val notificationManager: NotificationManager =
+ context.getSystemService(NotificationManager::class.java)
+
+ NotificationChannel(
+ NOTIFICATION_ID,
+ resources.getString(R.string.notification_start_up_channel_name),
+ NotificationManager.IMPORTANCE_DEFAULT
+ ).apply {
+ description = resources.getString(
+ R.string.notification_start_up_channel_description
+ )
+ notificationManager.createNotificationChannel(this)
+ }
+ }
+
+ val startupDelay = appPrefs.startUpDelay
+
+ if (startupDelay > 0) {
+ builder.setContentTitle(
+ context.getString(
+ R.string.notification_start_up_description_delay,
+ startupDelay
+ )
+ )
+ builder.setProgress(startupDelay, 0, true)
+
+ var delayCount = 0
+ for (i in startupDelay downTo 0) {
+ if (i == 0) {
+ builder.setProgress(0, 0, true)
+ builder.setContentTitle(
+ context.getString(R.string.notification_start_up_title)
+ )
+ builder.setContentText(
+ context.getString(R.string.notification_start_up_description)
+ )
+
+ notifyIfPossible(builder)
+ onShow(builder)
+ } else {
+ builder.setContentTitle(
+ context.getString(R.string.notification_start_up_description_delay, i)
+ )
+ builder.setProgress(startupDelay, delayCount, false)
+ notifyIfPossible(builder)
+ delay(1.seconds)
+ delayCount++
+ }
+ }
+
+ } else {
+ builder.setContentText(context.getString(R.string.notification_start_up_description))
+ notifyIfPossible(builder)
+ onShow(builder)
+ }
+ }
+
+ private fun notifyIfPossible(builder: NotificationCompat.Builder) {
+ if (ActivityCompat.checkSelfPermission(
+ context,
+ Manifest.permission.POST_NOTIFICATIONS
+ ) == PackageManager.PERMISSION_GRANTED ||
+ Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU
+ ) {
+ runCatching { notificationManager.notify(SERVICE_ID, builder.build()) }.onFailure { it.printStackTrace() }
+ }
+ }
+
+ companion object {
+ private const val SERVICE_ID: Int = 2
+ private const val NOTIFICATION_ID: String = "2"
+
+ fun enqueue(context: Context) {
+ val work: WorkRequest = OneTimeWorkRequestBuilder().build()
+ WorkManager.getInstance(context).enqueue(work)
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/work/TaskerWorker.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/work/TaskerWorker.kt
new file mode 100644
index 0000000..410750e
--- /dev/null
+++ b/app/src/main/kotlin/com/androidvip/sysctlgui/work/TaskerWorker.kt
@@ -0,0 +1,78 @@
+package com.androidvip.sysctlgui.work
+
+import android.content.Context
+import android.widget.Toast
+import androidx.work.CoroutineWorker
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
+import androidx.work.WorkRequest
+import androidx.work.WorkerParameters
+import androidx.work.workDataOf
+import com.androidvip.sysctlgui.R
+import com.androidvip.sysctlgui.domain.repository.AppPrefs
+import com.androidvip.sysctlgui.domain.usecase.ApplyParamsUseCase
+import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase
+import com.androidvip.sysctlgui.receivers.TaskerReceiver
+import com.androidvip.sysctlgui.toast
+import com.androidvip.sysctlgui.utils.Consts
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.withContext
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
+import kotlin.contracts.ExperimentalContracts
+
+@OptIn(ExperimentalContracts::class)
+class TaskerWorker(
+ private val context: Context,
+ workerParams: WorkerParameters
+) : CoroutineWorker(context, workerParams), KoinComponent {
+ private val mainContext = Dispatchers.Main + SupervisorJob()
+ private val workerContext = Dispatchers.IO
+ private val appPrefs: AppPrefs by inject()
+ private val getUserParamsUseCase: GetUserParamsUseCase by inject()
+ private val applyParamsUseCase: ApplyParamsUseCase by inject()
+
+ override suspend fun doWork(): Result {
+ withContext(workerContext) {
+ val taskerList = inputData.getInt(
+ TaskerReceiver.BUNDLE_EXTRA_LIST_NUMBER,
+ Consts.LIST_NUMBER_INVALID
+ )
+ applyParams(taskerList)
+ if (appPrefs.showTaskerToast) {
+ withContext(mainContext) {
+ context.toast(
+ context.getString(R.string.tasker_toast, taskerList),
+ Toast.LENGTH_LONG
+ )
+ }
+ }
+ }
+
+ return Result.success()
+ }
+
+ private suspend fun applyParams(listNumber: Int) {
+ val params = getUserParamsUseCase()
+ when (listNumber) {
+ Consts.LIST_NUMBER_PRIMARY_TASKER,
+ Consts.LIST_NUMBER_SECONDARY_TASKER -> params.filter { it.taskerParam }
+ Consts.LIST_NUMBER_FAVORITES -> params.filter { it.favorite }
+ Consts.LIST_NUMBER_APPLY_ON_BOOT -> params
+
+ else -> emptyList()
+ }.forEach {
+ applyParamsUseCase(it)
+ }
+ }
+
+ companion object {
+ fun enqueue(context: Context, listNumber: Int) {
+ val work: WorkRequest = OneTimeWorkRequestBuilder()
+ .setInputData(workDataOf(TaskerReceiver.BUNDLE_EXTRA_LIST_NUMBER to listNumber))
+ .build()
+ WorkManager.getInstance(context).enqueue(work)
+ }
+ }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index 7714bd8..d11fcfb 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,4 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id("com.google.devtools.ksp") version "1.9.24-1.0.20" apply false
+}
buildscript {
repositories {
@@ -7,7 +10,7 @@ buildscript {
}
dependencies {
- classpath(BuildPlugins.gradle)
+ classpath("com.android.tools.build:gradle:8.5.0")
classpath(BuildPlugins.kotlin)
}
}
diff --git a/buildSrc/src/main/kotlin/AndroidX.kt b/buildSrc/src/main/kotlin/AndroidX.kt
index abb26e8..6f22260 100644
--- a/buildSrc/src/main/kotlin/AndroidX.kt
+++ b/buildSrc/src/main/kotlin/AndroidX.kt
@@ -21,4 +21,7 @@ object AndroidX {
const val room = "androidx.room:room-ktx:$roomVersion"
const val roomRuntime = "androidx.room:room-runtime:$roomVersion"
const val roomCompiler = "androidx.room:room-compiler:$roomVersion"
+
+ private const val workManagerVersion = "2.9.0"
+ const val workManager = "androidx.work:work-runtime-ktx:$workManagerVersion"
}
diff --git a/buildSrc/src/main/kotlin/AppConfig.kt b/buildSrc/src/main/kotlin/AppConfig.kt
index e65986a..a2448da 100644
--- a/buildSrc/src/main/kotlin/AppConfig.kt
+++ b/buildSrc/src/main/kotlin/AppConfig.kt
@@ -2,9 +2,9 @@ object AppConfig {
val devCycle = false
const val appId = "com.androidvip.sysctlgui"
- const val compileSdkVersion = 33
+ const val compileSdkVersion = 34
const val minSdkVersion = 21
- const val targetSdlVersion = 33
+ const val targetSdkVersion = 34
const val testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
const val proguardConsumerRules = "consumer-rules.pro"
diff --git a/buildSrc/src/main/kotlin/BuildPlugins.kt b/buildSrc/src/main/kotlin/BuildPlugins.kt
index f8e25a5..168cf36 100644
--- a/buildSrc/src/main/kotlin/BuildPlugins.kt
+++ b/buildSrc/src/main/kotlin/BuildPlugins.kt
@@ -2,6 +2,6 @@ object BuildPlugins {
private const val agpVersion = "7.4.2"
const val gradle = "com.android.tools.build:gradle:$agpVersion"
- private const val kotlinVersion = "1.7.20"
+ private const val kotlinVersion = "1.9.24"
const val kotlin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
diff --git a/buildSrc/src/main/kotlin/Compose.kt b/buildSrc/src/main/kotlin/Compose.kt
index 2970f43..6ee5046 100644
--- a/buildSrc/src/main/kotlin/Compose.kt
+++ b/buildSrc/src/main/kotlin/Compose.kt
@@ -1,6 +1,6 @@
object Compose {
- const val BoM = "androidx.compose:compose-bom:2023.06.01"
- const val kotlinCompilerExtensionVersion = "1.3.2"
+ const val BoM = "androidx.compose:compose-bom:2024.06.00"
+ const val kotlinCompilerExtensionVersion = "1.5.14"
const val material3 = "androidx.compose.material3:material3"
const val material = "androidx.compose.material:material"
const val uiTooling = "androidx.compose.ui:ui-tooling"
diff --git a/common/design/build.gradle.kts b/common/design/build.gradle.kts
index 63cee28..e2da347 100644
--- a/common/design/build.gradle.kts
+++ b/common/design/build.gradle.kts
@@ -9,7 +9,7 @@ android {
defaultConfig {
minSdk = AppConfig.minSdkVersion
- targetSdk = AppConfig.targetSdlVersion
+ targetSdk = AppConfig.targetSdkVersion
testInstrumentationRunner = AppConfig.testInstrumentationRunner
consumerProguardFiles(AppConfig.proguardConsumerRules)
@@ -31,12 +31,12 @@ android {
}
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
- jvmTarget = "1.8"
+ jvmTarget = "17"
}
composeOptions {
diff --git a/common/design/src/main/java/com/androidvip/sysctlgui/design/DesignResources.kt b/common/design/src/main/java/com/androidvip/sysctlgui/design/DesignResources.kt
new file mode 100644
index 0000000..2a29b4f
--- /dev/null
+++ b/common/design/src/main/java/com/androidvip/sysctlgui/design/DesignResources.kt
@@ -0,0 +1,5 @@
+package com.androidvip.sysctlgui.design
+
+typealias DesignIds = com.androidvip.sysctlgui.design.R.id
+typealias DesignLayouts = com.androidvip.sysctlgui.design.R.layout
+typealias DesignStyles = com.androidvip.sysctlgui.design.R.style
diff --git a/common/utils/build.gradle.kts b/common/utils/build.gradle.kts
index f335143..6f0ac70 100644
--- a/common/utils/build.gradle.kts
+++ b/common/utils/build.gradle.kts
@@ -9,7 +9,7 @@ android {
defaultConfig {
minSdk = AppConfig.minSdkVersion
- targetSdk = AppConfig.targetSdlVersion
+ targetSdk = AppConfig.targetSdkVersion
testInstrumentationRunner = AppConfig.testInstrumentationRunner
consumerProguardFiles(AppConfig.proguardConsumerRules)
@@ -26,12 +26,12 @@ android {
}
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
- jvmTarget = "1.8"
+ jvmTarget = "17"
}
}
diff --git a/common/utils/src/main/AndroidManifest.xml b/common/utils/src/main/AndroidManifest.xml
index 1e004af..568741e 100644
--- a/common/utils/src/main/AndroidManifest.xml
+++ b/common/utils/src/main/AndroidManifest.xml
@@ -1,5 +1,2 @@
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/data/build.gradle.kts b/data/build.gradle.kts
index 5210a82..9e7f726 100644
--- a/data/build.gradle.kts
+++ b/data/build.gradle.kts
@@ -1,9 +1,7 @@
-import org.jetbrains.kotlin.config.KotlinCompilerVersion
-
plugins {
id("com.android.library")
kotlin("android")
- kotlin("kapt")
+ id("com.google.devtools.ksp")
}
android {
@@ -12,7 +10,7 @@ android {
defaultConfig {
minSdk = AppConfig.minSdkVersion
- targetSdk = AppConfig.targetSdlVersion
+ targetSdk = AppConfig.targetSdkVersion
javaCompileOptions {
annotationProcessorOptions {
arguments += mapOf(
@@ -31,16 +29,14 @@ android {
}
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = "1.8"
+ kotlin {
+ jvmToolchain(17)
}
-
-
sourceSets {
maybeCreate("main").java.srcDir("src/main/kotlin")
}
@@ -53,7 +49,7 @@ dependencies {
implementation(AndroidX.preference)
implementation(AndroidX.room)
implementation(AndroidX.roomRuntime)
- kapt(AndroidX.roomCompiler)
+ ksp(AndroidX.roomCompiler)
implementation(Dependencies.libSuCore)
implementation(Google.gson)
diff --git a/data/src/main/AndroidManifest.xml b/data/src/main/AndroidManifest.xml
index 835c133..8072ee0 100644
--- a/data/src/main/AndroidManifest.xml
+++ b/data/src/main/AndroidManifest.xml
@@ -1,6 +1,2 @@
-
-
-
-
+
diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts
index 47ce716..495951c 100644
--- a/domain/build.gradle.kts
+++ b/domain/build.gradle.kts
@@ -4,8 +4,8 @@ plugins {
}
java {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
dependencies {
diff --git a/gradle.properties b/gradle.properties
index 88d2460..e701fc8 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -19,9 +19,8 @@ android.useAndroidX=true
android.databinding.incremental=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
-# Incremental Processing
-kapt.incremental.apt=true
-kapt.include.compile.classpath=false
# Configuration cache
org.gradle.configuration-cache=true
+android.nonTransitiveRClass=true
+android.nonFinalResIds=false
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index f15a4ae..6372a16 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip