diff --git a/app/build.gradle b/app/build.gradle index 3d346431..fd484c6b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -128,28 +128,28 @@ dependencies { def etebaseVersion = '2.3.2' implementation "com.etebase:client:$etebaseVersion" + // ACRA kept at 5.7.0 in Phase 1: newer 5.x is built with Kotlin >= 1.9 and its + // metadata would be rejected by the Kotlin 1.8.21 compiler. Bump in Phase 2 with Kotlin. def acraVersion = '5.7.0' implementation "ch.acra:acra-http:$acraVersion" implementation "ch.acra:acra-dialog:$acraVersion" + // legacy-* artifacts only ever shipped at 1.0.0; core is bumped explicitly below. def supportVersion = '1.0.0' implementation "androidx.legacy:legacy-support-core-ui:$supportVersion" - implementation "androidx.core:core:$supportVersion" - implementation "androidx.appcompat:appcompat:1.1.0" + implementation "androidx.core:core:1.12.0" + implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.cardview:cardview:1.0.0" - // KTX extensions - implementation "androidx.core:core-ktx:1.3.1" - implementation "androidx.fragment:fragment-ktx:1.2.5" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0" - implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0" + // KTX extensions — capped at versions built with Kotlin <= 1.8 (Phase 1 keeps Kotlin 1.8.21). + implementation "androidx.core:core-ktx:1.12.0" + implementation "androidx.fragment:fragment-ktx:1.6.2" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0" - implementation 'com.google.android.material:material:1.2.0-beta01' + implementation 'com.google.android.material:material:1.12.0' implementation "androidx.legacy:legacy-preference-v14:$supportVersion" implementation 'com.github.yukuku:ambilwarna:2.0.1' - implementation ('com.github.worker8:tourguide:1.0.18-SNAPSHOT@aar') { - transitive = true - } def requeryVersion = '1.6.0' implementation "io.requery:requery:$requeryVersion" @@ -158,27 +158,28 @@ dependencies { kapt "io.requery:requery-processor:$requeryVersion" implementation 'com.google.code.findbugs:jsr305:3.0.2' - def okhttp3Version = "4.8.1" + // okhttp stays on 4.x for JDK8/minSdk21 compatibility; 4.12.0 fixes CVE-2021-0341. + def okhttp3Version = "4.12.0" implementation "com.squareup.okhttp3:okhttp:$okhttp3Version" implementation "com.squareup.okhttp3:logging-interceptor:$okhttp3Version" - implementation 'com.google.code.gson:gson:1.7.2' - implementation 'org.apache.commons:commons-collections4:4.1' - implementation 'org.apache.commons:commons-lang3:3.8.1' + implementation 'com.google.code.gson:gson:2.11.0' + implementation 'org.apache.commons:commons-collections4:4.4' + implementation 'org.apache.commons:commons-lang3:3.14.0' implementation 'com.github.cachapa:ExpandableLayout:2.9.2' implementation project(':cert4android') implementation project(':ical4android') implementation project(':vcard4android') // for tests - androidTestImplementation('androidx.test:runner:1.1.0-alpha4') { + androidTestImplementation('androidx.test:runner:1.5.2') { exclude group: 'com.android.support', module: 'support-annotations' } - androidTestImplementation('androidx.test:rules:1.1.0-alpha4') { + androidTestImplementation('androidx.test:rules:1.5.0') { exclude group: 'com.android.support', module: 'support-annotations' } - androidTestImplementation 'junit:junit:4.12' + androidTestImplementation 'junit:junit:4.13.2' androidTestImplementation "com.squareup.okhttp3:mockwebserver:$okhttp3Version" - testImplementation 'junit:junit:4.12' + testImplementation 'junit:junit:4.13.2' testImplementation "com.squareup.okhttp3:mockwebserver:$okhttp3Version" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c29a0001..0993a09a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,8 +6,7 @@ ~ which accompanies this distribution, and is available at ~ http://www.gnu.org/licenses/gpl.html --> - diff --git a/app/src/main/java/com/etesync/syncadapter/App.kt b/app/src/main/java/com/etesync/syncadapter/App.kt index 0e66529d..2133b3da 100644 --- a/app/src/main/java/com/etesync/syncadapter/App.kt +++ b/app/src/main/java/com/etesync/syncadapter/App.kt @@ -90,7 +90,9 @@ class App : Application() { tasksFilter.addAction(Intent.ACTION_PACKAGE_ADDED) tasksFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED) tasksFilter.addDataScheme("package") - registerReceiver(PackageChangedReceiver(), tasksFilter) + // Explicit export flag (required semantics on Android 14+); this receiver only needs + // the app's own process, so mark it not-exported. + ContextCompat.registerReceiver(this@App, PackageChangedReceiver(), tasksFilter, ContextCompat.RECEIVER_NOT_EXPORTED) TASK_PROVIDERS.forEach { // check whether a tasks app is currently installed diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.kt b/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.kt index a111d231..16065bbc 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.kt @@ -53,7 +53,6 @@ import com.etesync.syncadapter.ui.etebase.CollectionActivity import com.etesync.syncadapter.ui.etebase.InvitationsActivity import com.etesync.syncadapter.ui.setup.SetupUserInfoFragment import com.etesync.syncadapter.utils.HintManager -import com.etesync.syncadapter.utils.ShowcaseBuilder import com.etesync.syncadapter.utils.packageInstalled import com.google.android.material.snackbar.Snackbar import okhttp3.HttpUrl.Companion.toHttpUrlOrNull @@ -62,7 +61,6 @@ import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import tourguide.tourguide.ToolTip import java.util.logging.Level class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener, Refreshable { @@ -160,13 +158,6 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe } } - if (!HintManager.getHintSeen(this, HINT_VIEW_COLLECTION)) { - ShowcaseBuilder.getBuilder(this) - .setToolTip(ToolTip().setTitle(getString(R.string.tourguide_title)).setDescription(getString(R.string.account_showcase_view_collection))) - .playOn(tbCardDAV) - HintManager.setHintSeen(this, HINT_VIEW_COLLECTION, true) - } - if (settings.isLegacy) { if (!SetupUserInfoFragment.hasUserInfo(this, account)) { SetupUserInfoFragment.newInstance(account).show(supportFragmentManager, null) diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AccountsActivity.kt b/app/src/main/java/com/etesync/syncadapter/ui/AccountsActivity.kt index a7359feb..51f76de8 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/AccountsActivity.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/AccountsActivity.kt @@ -28,11 +28,9 @@ import com.etesync.syncadapter.Constants.serviceUrl import com.etesync.syncadapter.R import com.etesync.syncadapter.ui.setup.LoginActivity import com.etesync.syncadapter.utils.HintManager -import com.etesync.syncadapter.utils.ShowcaseBuilder import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.navigation.NavigationView import com.google.android.material.snackbar.Snackbar -import tourguide.tourguide.ToolTip class AccountsActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener, SyncStatusObserver { @@ -72,13 +70,6 @@ class AccountsActivity : BaseActivity(), NavigationView.OnNavigationItemSelected } PermissionsActivity.requestAllPermissions(this) - - if (!HintManager.getHintSeen(this, HINT_ACCOUNT_ADD)) { - ShowcaseBuilder.getBuilder(this) - .setToolTip(ToolTip().setTitle(getString(R.string.tourguide_title)).setDescription(getString(R.string.accounts_showcase_add)).setGravity(Gravity.TOP or Gravity.LEFT)) - .playOn(fab) - HintManager.setHintSeen(this, HINT_ACCOUNT_ADD, true) - } } override fun onResume() { diff --git a/app/src/main/java/com/etesync/syncadapter/ui/PermissionsActivity.kt b/app/src/main/java/com/etesync/syncadapter/ui/PermissionsActivity.kt index 33431ad5..3f5db26d 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/PermissionsActivity.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/PermissionsActivity.kt @@ -11,6 +11,7 @@ package com.etesync.syncadapter.ui import android.Manifest import android.app.Activity import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle import android.view.View import androidx.annotation.IdRes @@ -86,7 +87,12 @@ class PermissionsActivity : BaseActivity() { private val REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124 fun requestAllPermissions(activity: Activity) { - ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS) + TASK_PROVIDERS.flatMap { it.permissions.toList() }, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS) + var permissions = arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS) + TASK_PROVIDERS.flatMap { it.permissions.toList() } + // Android 13+ (API 33) requires the notification permission at runtime; without it the + // app's sync/status notifications are silently dropped. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) + permissions += Manifest.permission.POST_NOTIFICATIONS + ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS) } } } diff --git a/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.kt b/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.kt index 65c3649b..40ce531c 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.kt @@ -37,10 +37,8 @@ import com.etesync.syncadapter.resource.LocalTaskList import com.etesync.syncadapter.ui.importlocal.ImportActivity import com.etesync.syncadapter.ui.journalviewer.ListEntriesFragment import com.etesync.syncadapter.utils.HintManager -import com.etesync.syncadapter.utils.ShowcaseBuilder import com.etesync.syncadapter.utils.TaskProviderHandling import com.google.android.material.floatingactionbutton.FloatingActionButton -import tourguide.tourguide.ToolTip import java.io.FileNotFoundException import java.util.* @@ -115,16 +113,6 @@ class ViewCollectionActivity : BaseActivity(), Refreshable { refresh() - val title = findViewById(R.id.display_name) as TextView - if (!HintManager.getHintSeen(this, HINT_IMPORT)) { - val tourGuide = ShowcaseBuilder.getBuilder(this) - .setToolTip(ToolTip().setTitle(getString(R.string.tourguide_title)).setDescription(getString(R.string.account_showcase_import)).setGravity(Gravity.BOTTOM)) - .setPointer(null) - tourGuide.mOverlay.setHoleRadius(0) - tourGuide.playOn(title) - HintManager.setHintSeen(this, HINT_IMPORT, true) - } - val fab = findViewById(R.id.fab) fab.setOnClickListener { AlertDialog.Builder(this) diff --git a/app/src/main/java/com/etesync/syncadapter/ui/etebase/CollectionItemFragment.kt b/app/src/main/java/com/etesync/syncadapter/ui/etebase/CollectionItemFragment.kt index 6edb7ac4..92ef0e39 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/etebase/CollectionItemFragment.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/etebase/CollectionItemFragment.kt @@ -91,7 +91,7 @@ class CollectionItemFragment : Fragment() { R.id.on_send_event_invite -> { val account = accountHolder.account val intent = EventEmailInvitation(requireContext(), account).createIntent(emailInvitationEvent!!, emailInvitationEventString!!) - startActivity(intent) + intent?.let { startActivity(it) } } R.id.on_restore_item -> { restoreItem(accountHolder) diff --git a/app/src/main/java/com/etesync/syncadapter/ui/etebase/ViewCollectionFragment.kt b/app/src/main/java/com/etesync/syncadapter/ui/etebase/ViewCollectionFragment.kt index 752982c0..3387577b 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/etebase/ViewCollectionFragment.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/etebase/ViewCollectionFragment.kt @@ -17,9 +17,7 @@ import com.etesync.syncadapter.resource.LocalCalendar import com.etesync.syncadapter.ui.BaseActivity import com.etesync.syncadapter.ui.WebViewActivity import com.etesync.syncadapter.utils.HintManager -import com.etesync.syncadapter.utils.ShowcaseBuilder import com.google.android.material.floatingactionbutton.FloatingActionButton -import tourguide.tourguide.ToolTip import java.util.* class ViewCollectionFragment : Fragment() { @@ -44,15 +42,6 @@ class ViewCollectionFragment : Fragment() { private fun initUi(inflater: LayoutInflater, container: View, cachedCollection: CachedCollection) { val title = container.findViewById(R.id.display_name) - if (!HintManager.getHintSeen(requireContext(), HINT_IMPORT)) { - val tourGuide = ShowcaseBuilder.getBuilder(requireActivity()) - .setToolTip(ToolTip().setTitle(getString(R.string.tourguide_title)).setDescription(getString(R.string.account_showcase_import)).setGravity(Gravity.BOTTOM)) - .setPointer(null) - tourGuide.mOverlay.setHoleRadius(0) - tourGuide.playOn(title) - HintManager.setHintSeen(requireContext(), HINT_IMPORT, true) - } - val fab = container.findViewById(R.id.fab) fab?.setOnClickListener { AlertDialog.Builder(requireContext()) diff --git a/app/src/main/java/com/etesync/syncadapter/utils/ShowcaseBuilder.kt b/app/src/main/java/com/etesync/syncadapter/utils/ShowcaseBuilder.kt deleted file mode 100644 index 27c3b6b3..00000000 --- a/app/src/main/java/com/etesync/syncadapter/utils/ShowcaseBuilder.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.etesync.syncadapter.utils - -import android.app.Activity -import tourguide.tourguide.Overlay -import tourguide.tourguide.Pointer -import tourguide.tourguide.TourGuide - -object ShowcaseBuilder { - fun getBuilder(activity: Activity): TourGuide { - val ret = TourGuide.init(activity).with(TourGuide.Technique.CLICK) - .setPointer(Pointer()) - ret.setOverlay(Overlay().setOnClickListener { ret.cleanUp() }) - - return ret - } -} diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 63166bd4..601e631c 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -31,6 +31,9 @@ #81D4FA #039BE5 #000000 + + #FFFFFFFF #d32f2f #E8F4FD diff --git a/build.gradle b/build.gradle index 918a9c39..f57a838b 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ ext { kotlin_version = '1.8.21' - gradle_version = '8.2.2' + gradle_version = '8.5.2' compileSdkVersion = 36 buildToolsVersion = '36.0.0' } @@ -16,11 +16,11 @@ ext { // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = '1.8.21' - ext.gradle_version = '8.2.2' + ext.gradle_version = '8.5.2' repositories { - jcenter() google() + mavenCentral() } dependencies { classpath "com.android.tools.build:gradle:$gradle_version" @@ -30,13 +30,12 @@ buildscript { allprojects { repositories { - jcenter() + google() mavenCentral() maven() { url 'https://oss.sonatype.org/content/repositories/snapshots' } maven { url 'https://jitpack.io' } - google() } } diff --git a/gradle.properties b/gradle.properties index 0c0632ee..e63d2bdd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,7 @@ -android.enableJetifier=true android.useAndroidX=true org.gradle.jvmargs=-Xmx2048M + +# AGP 8.5.2 supports compileSdk up to 34; we build against 36 (Phase 1 conservative +# toolchain). Suppress the "not tested with this compileSdk" warning. Remove in Phase 2 +# once AGP is bumped to a version that officially supports compileSdk 36. +android.suppressUnsupportedCompileSdk=36 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0dea4b39..91d169be 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Aug 19 17:33:20 IDT 2020 +#Fri Jul 17 21:23:12 CEST 2026 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip