From 6d108dd7ffe63cc66c701a4d583c178ec5aca86e Mon Sep 17 00:00:00 2001 From: tom5079 <7948651+tom5079@users.noreply.github.com> Date: Sun, 14 Jan 2024 14:30:17 -0800 Subject: [PATCH] fix backup, notification for android 33+ --- app/build.gradle | 16 +-- app/release/output-metadata.json | 2 +- app/src/main/AndroidManifest.xml | 5 +- .../quaver/pupil/services/DownloadService.kt | 17 ++- .../java/xyz/quaver/pupil/ui/MainActivity.kt | 31 +++- .../xyz/quaver/pupil/ui/ReaderActivity.kt | 30 ++-- .../ui/fragment/ManageFavoritesFragment.kt | 134 ++++++++---------- .../main/java/xyz/quaver/pupil/util/misc.kt | 39 ++++- .../main/java/xyz/quaver/pupil/util/update.kt | 5 + app/src/main/res/values-ja/strings.xml | 1 + app/src/main/res/values-ko/strings.xml | 1 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/file_paths.xml | 5 +- build.gradle | 10 +- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 16 files changed, 188 insertions(+), 114 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 10b7e7fc..d484c9f9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,13 +32,13 @@ configurations { } android { - compileSdkVersion 32 + compileSdkVersion 34 defaultConfig { applicationId "xyz.quaver.pupil" minSdkVersion 16 - targetSdkVersion 32 + targetSdkVersion 34 versionCode 69 - versionName "5.3.8-hotfix1" + versionName "5.3.9" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true } @@ -79,7 +79,7 @@ android { dependencies { implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2" implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.3.2" @@ -95,15 +95,15 @@ dependencies { implementation "com.daimajia.swipelayout:library:1.2.0@aar" - implementation "com.google.android.material:material:1.5.0" + implementation "com.google.android.material:material:1.11.0" - implementation platform('com.google.firebase:firebase-bom:29.0.3') + implementation platform('com.google.firebase:firebase-bom:32.7.0') implementation "com.google.firebase:firebase-analytics-ktx" implementation "com.google.firebase:firebase-crashlytics-ktx" implementation "com.google.firebase:firebase-perf-ktx" - implementation "com.google.android.gms:play-services-oss-licenses:17.0.0" - implementation "com.google.android.gms:play-services-mlkit-face-detection:17.0.1" + implementation "com.google.android.gms:play-services-oss-licenses:17.0.1" + implementation "com.google.android.gms:play-services-mlkit-face-detection:17.1.0" implementation "com.github.clans:fab:1.6.4" diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index bb38ac22..fe625af8 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -12,7 +12,7 @@ "filters": [], "attributes": [], "versionCode": 69, - "versionName": "5.3.8-hotfix1", + "versionName": "5.3.9", "outputFile": "app-release.apk" } ], diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4164f493..d173f782 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,8 @@ + + @@ -45,7 +47,8 @@ + android:exported="false" + android:foregroundServiceType="specialUse" /> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) @@ -424,7 +431,11 @@ class DownloadService : Service() { override fun onBind(p0: Intent?) = binder override fun onCreate() { - startForeground(R.id.downloader_notification_id, serviceNotification.build()) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) { + startForeground(R.id.downloader_notification_id, serviceNotification.build()) + } else { + startForeground(R.id.downloader_notification_id, serviceNotification.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE) + } interceptors[Tag::class] = interceptor } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt index a242e2b1..e862fcc0 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -18,8 +18,10 @@ package xyz.quaver.pupil.ui +import android.Manifest import android.annotation.SuppressLint import android.content.Intent +import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle @@ -31,7 +33,9 @@ import android.view.View import android.view.animation.DecelerateInterpolator import android.widget.EditText import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog +import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.view.GravityCompat import androidx.core.view.ViewCompat @@ -58,10 +62,12 @@ import xyz.quaver.pupil.ui.view.MainView import xyz.quaver.pupil.ui.view.ProgressCard import xyz.quaver.pupil.util.ItemClickSupport import xyz.quaver.pupil.util.Preferences +import xyz.quaver.pupil.util.requestNotificationPermission import xyz.quaver.pupil.util.checkUpdate import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.DownloadManager import xyz.quaver.pupil.util.restore +import xyz.quaver.pupil.util.showNotificationPermissionExplanationDialog import java.util.regex.Pattern import kotlin.math.ceil import kotlin.math.max @@ -107,6 +113,12 @@ class MainActivity : private lateinit var binding: MainActivityBinding + private val requestNotificationPermssionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (!isGranted) { + showNotificationPermissionExplanationDialog(this) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = MainActivityBinding.inflate(layoutInflater) @@ -124,6 +136,8 @@ class MainActivity : } } + requestNotificationPermission(this, requestNotificationPermssionLauncher, false) {} + if (Preferences["download_folder", ""].isEmpty()) DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog") @@ -392,12 +406,17 @@ class MainActivity : onDownloadClickedHandler = { position -> val galleryID = galleries[position] - if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress - DownloadService.cancel(this@MainActivity, galleryID) - } - else { - DownloadManager.getInstance(context).addDownloadFolder(galleryID) - DownloadService.download(this@MainActivity, galleryID) + requestNotificationPermission( + this@MainActivity, + requestNotificationPermssionLauncher + ) { + if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress + DownloadService.cancel(this@MainActivity, galleryID) + } + else { + DownloadManager.getInstance(context).addDownloadFolder(galleryID) + DownloadService.download(this@MainActivity, galleryID) + } } closeAllItems() diff --git a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt index b1670c15..3f665b35 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt @@ -57,9 +57,12 @@ import xyz.quaver.pupil.favorites import xyz.quaver.pupil.services.DownloadService import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.camera +import xyz.quaver.pupil.util.checkNotificationEnabled import xyz.quaver.pupil.util.closeCamera import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.DownloadManager +import xyz.quaver.pupil.util.requestNotificationPermission +import xyz.quaver.pupil.util.showNotificationPermissionExplanationDialog import xyz.quaver.pupil.util.startCamera class ReaderActivity : BaseActivity() { @@ -117,6 +120,12 @@ class ReaderActivity : BaseActivity() { private lateinit var binding: ReaderActivityBinding + private val requestNotificationPermssionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (!isGranted) { + showNotificationPermissionExplanationDialog(this) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ReaderActivityBinding.inflate(layoutInflater) @@ -360,15 +369,20 @@ class ReaderActivity : BaseActivity() { animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button setOnClickListener { - val downloadManager = DownloadManager.getInstance(this@ReaderActivity) + requestNotificationPermission( + this@ReaderActivity, + requestNotificationPermssionLauncher + ) { + val downloadManager = DownloadManager.getInstance(this@ReaderActivity) - if (downloadManager.isDownloading(galleryID)) { - downloadManager.deleteDownloadFolder(galleryID) - animateDownloadFAB(false) - } else { - downloadManager.addDownloadFolder(galleryID) - DownloadService.download(context, galleryID, true) - animateDownloadFAB(true) + if (downloadManager.isDownloading(galleryID)) { + downloadManager.deleteDownloadFolder(galleryID) + animateDownloadFAB(false) + } else { + downloadManager.addDownloadFolder(galleryID) + DownloadService.download(context, galleryID, true) + animateDownloadFAB(true) + } } } } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/fragment/ManageFavoritesFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/fragment/ManageFavoritesFragment.kt index 55731398..684442e2 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/fragment/ManageFavoritesFragment.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/fragment/ManageFavoritesFragment.kt @@ -18,15 +18,19 @@ package xyz.quaver.pupil.ui.fragment +import android.app.Activity import android.content.Intent import android.content.res.Resources import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.os.Bundle +import android.util.Log import android.widget.EditText import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider import androidx.core.graphics.drawable.DrawableCompat import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat @@ -34,11 +38,19 @@ import androidx.swiperefreshlayout.widget.CircularProgressDrawable import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch +import kotlinx.datetime.LocalDate import kotlinx.serialization.json.Json import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.decodeFromJsonElement import okhttp3.* +import xyz.quaver.io.FileX +import xyz.quaver.io.util.readText import xyz.quaver.pupil.R import xyz.quaver.pupil.client +import xyz.quaver.pupil.favoriteTags +import xyz.quaver.pupil.favorites +import xyz.quaver.pupil.types.Tag +import xyz.quaver.pupil.util.get import xyz.quaver.pupil.util.restore import java.io.File import java.io.IOException @@ -46,6 +58,28 @@ import kotlin.math.roundToInt class ManageFavoritesFragment : PreferenceFragmentCompat() { + private val requestBackupFileLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode != Activity.RESULT_OK) { + return@registerForActivityResult + } + + val uri = result.data?.data ?: return@registerForActivityResult + val context = context ?: return@registerForActivityResult + + val backupData = runCatching { + FileX(context, uri).readText()?.let { Json.parseToJsonElement(it) } + }.getOrNull() ?: return@registerForActivityResult + + val newFavorites = backupData["favorites"]?.let { Json.decodeFromJsonElement>(it) }.orEmpty() + val newFavoriteTags = backupData["favorite_tags"]?.let { Json.decodeFromJsonElement>(it) }.orEmpty() + + favorites.addAll(newFavorites) + favoriteTags.addAll(newFavoriteTags) + + val view = view ?: return@registerForActivityResult + Snackbar.make(view, context.getString(R.string.settings_restore_success, newFavorites.size + newFavoriteTags.size), Snackbar.LENGTH_LONG).show() + } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.manage_favorites_preferences, rootKey) @@ -56,25 +90,6 @@ class ManageFavoritesFragment : PreferenceFragmentCompat() { val context = context ?: return findPreference("backup")?.setOnPreferenceClickListener { - val iconSize = (24 * Resources.getSystem().displayMetrics.density).roundToInt() - val strokeWidth = (3 * Resources.getSystem().displayMetrics.density) - - val icon = object: CircularProgressDrawable(context) { - override fun getIntrinsicHeight(): Int { - return iconSize - } - - override fun getIntrinsicWidth(): Int { - return iconSize - } - } - - icon.strokeWidth = strokeWidth - icon.colorFilter = PorterDuffColorFilter(ContextCompat.getColor(context, R.color.colorAccent), PorterDuff.Mode.SRC_IN) - DrawableCompat.setTint(icon, ContextCompat.getColor(context, R.color.colorAccent)) - icon.start() - it.icon = icon - val favorites = runCatching { Json.parseToJsonElement(File(ContextCompat.getDataDir(context), "favorites.json").readText()) }.getOrNull() @@ -82,72 +97,37 @@ class ManageFavoritesFragment : PreferenceFragmentCompat() { Json.parseToJsonElement(File(ContextCompat.getDataDir(context), "favorites_tags.json").readText()) }.getOrNull() - val request = Request.Builder() - .url(context.getString(R.string.backup_url)) - .post( - FormBody.Builder() - .add("f:1", buildJsonObject { - favorites?.let { - put("favorites", it) - } - favoriteTags?.let { - put("favorite_tags", it) - } - }.toString()) - .build() - ).build() - - client.newCall(request).enqueue(object: Callback { - override fun onFailure(call: Call, e: IOException) { - val view = view ?: return - - MainScope().launch { - it.icon = null - } - Snackbar.make(view, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show() + val favoriteJson = buildJsonObject { + favorites?.let { + put("favorites", it) } - - override fun onResponse(call: Call, response: Response) { - MainScope().launch { - it.icon = null - } - - if (response.code() != 200) { - response.close() - return - } - - Intent(Intent.ACTION_SEND).apply { - type = "text/plain" - putExtra(Intent.EXTRA_TEXT, response.body()?.use { it.string() }?.replace("\n", "")) - }.let { - getContext()?.startActivity(Intent.createChooser(it, getString(R.string.settings_backup_share))) - } + favoriteTags?.let { + put("favorite_tags", it) } - }) + } + + val backupFile = File(context.filesDir, "pupil-backup.json").also { + it.writeText(favoriteJson.toString()) + } + + Intent(Intent.ACTION_SEND).apply { + val uri = FileProvider.getUriForFile(context, "${context.packageName}.provider", backupFile) + setDataAndType(uri, "application/json") + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + putExtra(Intent.EXTRA_STREAM, uri) + }.let { + context.startActivity(Intent.createChooser(it, getString(R.string.settings_backup_share))) + } true } findPreference("restore")?.setOnPreferenceClickListener { - val editText = EditText(context).apply { - setText(context.getString(R.string.backup_url), TextView.BufferType.EDITABLE) + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/json" } - AlertDialog.Builder(context) - .setTitle(R.string.settings_restore_title) - .setView(editText) - .setPositiveButton(android.R.string.ok) { _, _ -> - restore(editText.text.toString(), - onFailure = onFailure@{ - val view = view ?: return@onFailure - Snackbar.make(view, R.string.settings_restore_failed, Snackbar.LENGTH_LONG).show() - }, onSuccess = onSuccess@{ - val view = view ?: return@onSuccess - Snackbar.make(view, context.getString(R.string.settings_restore_success, it), Snackbar.LENGTH_LONG).show() - }) - }.setNegativeButton(android.R.string.cancel) { _, _ -> - // Do Nothing - }.show() + requestBackupFileLauncher.launch(intent) true } diff --git a/app/src/main/java/xyz/quaver/pupil/util/misc.kt b/app/src/main/java/xyz/quaver/pupil/util/misc.kt index 8e72a210..9e666680 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/misc.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/misc.kt @@ -18,11 +18,21 @@ package xyz.quaver.pupil.util +import android.Manifest import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import androidx.activity.result.ActivityResultLauncher +import androidx.appcompat.app.AlertDialog +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import com.google.firebase.crashlytics.FirebaseCrashlytics import kotlinx.serialization.json.* import okhttp3.OkHttpClient import okhttp3.Request +import xyz.quaver.pupil.R import xyz.quaver.pupil.hitomi.GalleryBlock import xyz.quaver.pupil.hitomi.GalleryInfo import xyz.quaver.pupil.hitomi.imageUrlFromImage @@ -132,4 +142,31 @@ fun JsonElement.getOrNull(tag: String) = kotlin.runCatching { }.getOrNull() val JsonElement.content - get() = this.jsonPrimitive.contentOrNull \ No newline at end of file + get() = this.jsonPrimitive.contentOrNull + +fun checkNotificationEnabled(context: Context) = + Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || + ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED + +fun showNotificationPermissionExplanationDialog(context: Context) { + AlertDialog.Builder(context) + .setTitle(R.string.warning) + .setMessage(R.string.notification_denied) + .setPositiveButton(android.R.string.ok) { _, _ -> } + .show() +} + +fun requestNotificationPermission( + activity: Activity, + requestPermissionLauncher: ActivityResultLauncher, + showRationale: Boolean = true, + ifGranted: () -> Unit, +) { + when { + checkNotificationEnabled(activity) -> ifGranted() + showRationale && ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.POST_NOTIFICATIONS) -> + showNotificationPermissionExplanationDialog(activity) + else -> + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/util/update.kt b/app/src/main/java/xyz/quaver/pupil/util/update.kt index 4b058b9b..e07a4e16 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/update.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/update.kt @@ -135,6 +135,11 @@ fun checkUpdate(context: Context, force: Boolean = false) { val msg = extractReleaseNote(update, Locale.getDefault()) setMessage(Markwon.create(context).toMarkdown(msg)) setPositiveButton(android.R.string.ok) { _, _ -> + if (!checkNotificationEnabled(context)) { + showNotificationPermissionExplanationDialog(context) + return@setPositiveButton + } + val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager //Cancel any download queued before diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index de1fcf06..ea44ad2f 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -22,6 +22,7 @@ 履歴を削除しますか? 履歴数: %1$d 履歴 + 通知を無効にするとバックグラウンドダウンロード及びアプリのアップデート機能が使用不可になります。 トップ # リリースノート(v%1$s)\n%2$s セキュリティーモード diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 0e4f93fe..8554abb8 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -21,6 +21,7 @@ 기록을 삭제하시겠습니까? 기록 %1$d개 저장됨 기록 + 백그라운드 다운로드를 위해서는 알림을 활성화할 필요가 있습니다. 알림을 비활성화하면 백그라운드 다운로드와 앱 업데이트 기능을 사용할 수 없습니다. # 릴리즈 노트(v%1$s)\n%2$s 최근 앱 목록 창에서 앱 화면을 보이지 않게 합니다 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c813e482..c2cc2bbf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -51,6 +51,8 @@ From Android 11 and above, current Download folder cannot be accessed by outside apps. Would you like to change the download folder? + Notification permission is required for background downloads. If you deny notifications from this app, in-app update and background download will be disabled. + Home History Downloads diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml index 25ccb28c..ebe57e8a 100644 --- a/app/src/main/res/xml/file_paths.xml +++ b/app/src/main/res/xml/file_paths.xml @@ -18,6 +18,7 @@ --> - - + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index f2aec97c..bd179094 100644 --- a/build.gradle +++ b/build.gradle @@ -6,16 +6,16 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:7.3.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - classpath "com.google.gms:google-services:4.3.10" + classpath "com.google.gms:google-services:4.3.15" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files - classpath "com.google.firebase:firebase-crashlytics-gradle:2.8.1" - classpath "com.google.firebase:perf-plugin:1.4.1" - classpath "com.google.android.gms:oss-licenses-plugin:0.10.5" + classpath "com.google.firebase:firebase-crashlytics-gradle:2.9.9" + classpath "com.google.firebase:perf-plugin:1.4.2" + classpath "com.google.android.gms:oss-licenses-plugin:0.10.6" } } diff --git a/gradle.properties b/gradle.properties index f695ad71..f7137ba7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,4 @@ kotlin.code.style=official android.enableJetifier=true android.useAndroidX=true -kotlin_version=1.7.10 \ No newline at end of file +kotlin_version=1.9.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 563bd40f..cc366756 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.3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip