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