diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 75d474f1..0c3bb70d 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -30,15 +30,6 @@ - - + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460d..00000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 095cfd15..a0911980 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,9 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt' apply plugin: 'com.google.firebase.crashlytics' +apply plugin: 'de.mannodermaus.android-junit5' dependencies { // Android Support @@ -15,14 +16,13 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" - implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_extension_version" implementation "androidx.core:core-ktx:$core_version" implementation "androidx.collection:collection-ktx:$collection_version" implementation "androidx.browser:browser:$browser_version" implementation "androidx.fragment:fragment-ktx:$fragment_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" - implementation "androidx.multidex:multidex:$multidex_version" implementation "androidx.paging:paging-runtime-ktx:$paging_version" coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugar_jdk_libs_version" @@ -33,9 +33,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" // koin - implementation "org.koin:koin-android:$koin_version" - implementation "org.koin:koin-androidx-viewmodel:$koin_version" - implementation "org.koin:koin-androidx-scope:$koin_version" + implementation "io.insert-koin:koin-android:$koin_version" // Geocaching API api project(":geocaching-api") @@ -53,21 +51,17 @@ dependencies { // Crashlytics & Firebase implementation platform("com.google.firebase:firebase-bom:$firebase_bom_version") - implementation 'com.google.firebase:firebase-core' - implementation 'com.google.firebase:firebase-crashlytics' - implementation 'com.google.firebase:firebase-analytics' + implementation 'com.google.firebase:firebase-crashlytics-ktx' + implementation 'com.google.firebase:firebase-analytics-ktx' // Networking implementation "com.squareup.okhttp3:okhttp:$okhttp_version" } android { - compileSdkVersion rootProject.compile_sdk_version + namespace 'com.arcao.geocaching4locus' - dexOptions { - // Skip pre-dexing when running on Travis CI or when disabled via -Dpre-dex=false. - preDexLibraries preDexEnabled && !isTravis - } + compileSdkVersion rootProject.compile_sdk_version defaultConfig { applicationId 'com.arcao.geocaching4locus' @@ -93,8 +87,8 @@ android { buildConfigField 'String', 'TEST_USER', 'null' buildConfigField 'String', 'TEST_PASSWORD', 'null' - resConfigs 'en', 'cs', 'de', 'es', 'fr', 'nl', 'no', 'pl', 'sk' + } compileOptions { @@ -106,8 +100,8 @@ android { kotlinOptions { freeCompilerArgs += [ - "-Xopt-in=kotlin.RequiresOptIn", - "-Xjvm-default=compatibility" + "-opt-in=kotlin.RequiresOptIn", + "-Xjvm-default=all-compatibility" ] jvmTarget = JavaVersion.VERSION_1_8 } @@ -139,7 +133,8 @@ android { buildConfigField 'String', 'BUILD_TIME', '"' + gitTimestamp() + '"' signingConfig signingConfigs.release - minifyEnabled false // App crash on VerifyError on Android 4.2, 4.3; https://issuetracker.google.com/issues/134304597 + minifyEnabled false + // App crash on VerifyError on Android 4.2, 4.3; https://issuetracker.google.com/issues/134304597 shrinkResources false crunchPngs false @@ -147,20 +142,22 @@ android { proguardFiles fileTree(dir: 'proguard-rules', include: '*.pro').getFiles().toArray() } } - packagingOptions { - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/LICENSE' - exclude 'META-INF/atomicfu.kotlin_module' - exclude 'org/apache/http/version.properties' - exclude 'templates/release-notes.vm' - exclude 'log4j.xml' + resources { + excludes += [ + 'META-INF/DEPENDENCIES', + 'META-INF/LICENSE', + 'META-INF/atomicfu.kotlin_module', + 'org/apache/http/version.properties', + 'templates/release-notes.vm', + 'log4j.xml' + ] + } } - lintOptions { abortOnError false } -} -androidExtensions { - experimental = true + lint { + abortOnError false + } } if (project.hasProperty('storeFile') && diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fa76d27d..2e7a1b2b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ @@ -38,11 +37,13 @@ android:name=".App" android:allowBackup="true" android:backupAgent=".base.util.backup.PreferencesBackupAgent" + android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/full_backup_scheme" android:hardwareAccelerated="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" - android:largeHeap="true"> + android:largeHeap="true" + tools:targetApi="s"> @@ -183,6 +184,12 @@ android:host="*.coord.info" android:pathPrefix="/GC" android:scheme="https" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/arcao/feedback/collector/ConfigurationCollector.kt b/app/src/main/java/com/arcao/feedback/collector/ConfigurationCollector.kt index 02d56c91..bde37c3f 100644 --- a/app/src/main/java/com/arcao/feedback/collector/ConfigurationCollector.kt +++ b/app/src/main/java/com/arcao/feedback/collector/ConfigurationCollector.kt @@ -6,7 +6,6 @@ import android.util.SparseArray import timber.log.Timber import java.lang.reflect.Field import java.lang.reflect.Modifier -import java.util.Locale class ConfigurationCollector(private val context: Context) : Collector() { override val name: String @@ -150,14 +149,17 @@ class ConfigurationCollector(private val context: Context) : Collector() { * @throws IllegalAccessException if the supplied field is inaccessible. */ @Throws(IllegalAccessException::class) - private fun getFieldValueName(conf: Configuration, f: Field): String? { + private fun getFieldValueName(conf: Configuration, f: Field): String { when (val fieldName = f.name) { FIELD_MCC, FIELD_MNC -> return f.getInt(conf).toString() FIELD_UIMODE -> return activeFlags(VALUE_ARRAYS[PREFIX_UI_MODE]!!, f.getInt(conf)) - FIELD_SCREENLAYOUT -> return activeFlags(VALUE_ARRAYS[PREFIX_SCREENLAYOUT]!!, f.getInt(conf)) + FIELD_SCREENLAYOUT -> return activeFlags( + VALUE_ARRAYS[PREFIX_SCREENLAYOUT]!!, + f.getInt(conf) + ) else -> { val values = - VALUE_ARRAYS[fieldName.toUpperCase(Locale.ROOT) + '_'] // Unknown field, return the raw int as String + VALUE_ARRAYS[fieldName.uppercase() + '_'] // Unknown field, return the raw int as String ?: return f.getInt(conf).toString() return values.get(f.getInt(conf)) // Unknown value, return the raw int as String diff --git a/app/src/main/java/com/arcao/feedback/collector/DisplayManagerCollector.kt b/app/src/main/java/com/arcao/feedback/collector/DisplayManagerCollector.kt index aa5b7b70..0da7b6e7 100644 --- a/app/src/main/java/com/arcao/feedback/collector/DisplayManagerCollector.kt +++ b/app/src/main/java/com/arcao/feedback/collector/DisplayManagerCollector.kt @@ -4,12 +4,10 @@ import android.content.Context import android.graphics.Point import android.graphics.Rect import android.hardware.display.DisplayManager -import android.os.Build import android.util.DisplayMetrics import android.util.SparseArray import android.view.Display import android.view.Surface -import android.view.WindowManager import timber.log.Timber class DisplayManagerCollector(private val context: Context) : Collector() { @@ -20,20 +18,13 @@ class DisplayManagerCollector(private val context: Context) : Collector() { var displays: Array? = null val result = StringBuilder() - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { - // Before Android 4.2, there was a single display available from the - // window manager - val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager - displays = arrayOf(windowManager.defaultDisplay) - } else { - // Since Android 4.2, we can fetch multiple displays with the - // DisplayManager. - try { - val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager - displays = displayManager.displays - } catch (e: IllegalArgumentException) { - Timber.e(e, "Error while collecting DisplayManager data") - } + // Since Android 4.2, we can fetch multiple displays with the + // DisplayManager. + try { + val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + displays = displayManager.displays + } catch (e: IllegalArgumentException) { + Timber.e(e, "Error while collecting DisplayManager data") } displays?.forEach { display -> @@ -102,6 +93,7 @@ class DisplayManagerCollector(private val context: Context) : Collector() { try { // since API v13 val size = Rect() + @Suppress("DEPRECATION") display.getRectSize(size) result.append(display.displayId).append(".rectSize=[").append(size.top).append(',').append(size.left) .append(',').append(size.width()).append(',').append(size.height()).append(']').append('\n') diff --git a/app/src/main/java/com/arcao/feedback/collector/LogCatCollector.kt b/app/src/main/java/com/arcao/feedback/collector/LogCatCollector.kt index 2ea5c7ed..eb0cfc7c 100644 --- a/app/src/main/java/com/arcao/feedback/collector/LogCatCollector.kt +++ b/app/src/main/java/com/arcao/feedback/collector/LogCatCollector.kt @@ -9,6 +9,7 @@ class LogCatCollector : Collector() { override val name: String get() = "LOGCAT" + @Suppress("BlockingMethodInNonBlockingContext", "ControlFlowWithEmptyBody") override suspend fun collect(): String = coroutineScope { try { val process = Runtime.getRuntime().exec(COMMAND_LINE) diff --git a/app/src/main/java/com/arcao/feedback/collector/MemoryCollector.kt b/app/src/main/java/com/arcao/feedback/collector/MemoryCollector.kt index 910ca5af..3d621bb5 100644 --- a/app/src/main/java/com/arcao/feedback/collector/MemoryCollector.kt +++ b/app/src/main/java/com/arcao/feedback/collector/MemoryCollector.kt @@ -8,6 +8,7 @@ class MemoryCollector : Collector() { override val name: String get() = "MEMORY" + @Suppress("BlockingMethodInNonBlockingContext") override suspend fun collect(): String { try { val commandLine = arrayOf("dumpsys", "meminfo", Process.myPid().toString()) diff --git a/app/src/main/java/com/arcao/geocaching4locus/App.kt b/app/src/main/java/com/arcao/geocaching4locus/App.kt index 31197558..4285851f 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/App.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/App.kt @@ -3,14 +3,10 @@ package com.arcao.geocaching4locus import android.app.Application -import android.content.Context -import android.os.Build import android.webkit.CookieManager -import android.webkit.CookieSyncManager import androidx.annotation.WorkerThread import androidx.core.content.edit import androidx.core.content.pm.PackageInfoCompat -import androidx.multidex.MultiDex import androidx.preference.PreferenceManager import com.arcao.feedback.feedbackModule import com.arcao.geocaching4locus.authentication.util.isPremium @@ -46,7 +42,7 @@ class App : Application() { putString(PrefConstants.DEVICE_ID, value) } } - value.orEmpty() + value } val version: String by lazy { @@ -89,11 +85,6 @@ class App : Application() { analyticsManager.setPremiumMember(accountManager.isPremium) } - override fun attachBaseContext(base: Context) { - super.attachBaseContext(base) - MultiDex.install(this) - } - private fun prepareCrashlytics() { // Set up Crashlytics, disabled for debug builds if (BuildConfig.DEBUG) { @@ -114,8 +105,8 @@ class App : Application() { null } - crashlytics.setCustomKey(CrashlyticsConstants.LOCUS_VERSION, lv?.versionName ?: "") - crashlytics.setCustomKey(CrashlyticsConstants.LOCUS_PACKAGE, lv?.packageName ?: "") + crashlytics.setCustomKey(CrashlyticsConstants.LOCUS_VERSION, lv?.versionName.orEmpty()) + crashlytics.setCustomKey(CrashlyticsConstants.LOCUS_PACKAGE, lv?.packageName.orEmpty()) } @WorkerThread @@ -134,12 +125,7 @@ class App : Application() { } private fun flushCookie() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - CookieManager.getInstance().flush() - } else { - @Suppress("DEPRECATION") - CookieSyncManager.createInstance(this).sync() - } + CookieManager.getInstance().flush() } companion object { diff --git a/app/src/main/java/com/arcao/geocaching4locus/AppModule.kt b/app/src/main/java/com/arcao/geocaching4locus/AppModule.kt index c259dac0..fcd4d2c2 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/AppModule.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/AppModule.kt @@ -1,6 +1,5 @@ package com.arcao.geocaching4locus -import android.content.Intent import com.arcao.geocaching4locus.authentication.LoginViewModel import com.arcao.geocaching4locus.authentication.usecase.CreateAccountUseCase import com.arcao.geocaching4locus.authentication.usecase.RetrieveAuthorizationUrlUseCase @@ -27,7 +26,6 @@ import com.arcao.geocaching4locus.base.usecase.RemoveLocusMapPointsUseCase import com.arcao.geocaching4locus.base.usecase.RequireLocationPermissionRequestUseCase import com.arcao.geocaching4locus.base.usecase.SendPointsSilentToLocusMapUseCase import com.arcao.geocaching4locus.base.usecase.WritePointToPackPointsFileUseCase -import com.arcao.geocaching4locus.base.usecase.entity.GeocacheListEntity import com.arcao.geocaching4locus.base.util.AnalyticsManager import com.arcao.geocaching4locus.dashboard.DashboardViewModel import com.arcao.geocaching4locus.data.account.AccountManager @@ -36,8 +34,6 @@ import com.arcao.geocaching4locus.error.handler.ExceptionHandler import com.arcao.geocaching4locus.import_bookmarks.ImportBookmarkViewModel import com.arcao.geocaching4locus.import_bookmarks.fragment.BookmarkListViewModel import com.arcao.geocaching4locus.import_bookmarks.fragment.BookmarkViewModel -import com.arcao.geocaching4locus.import_bookmarks.paging.GeocacheUserListsDataSourceFactory -import com.arcao.geocaching4locus.import_bookmarks.paging.ListGeocachesDataSourceFactory import com.arcao.geocaching4locus.importgc.ImportGeocacheCodeViewModel import com.arcao.geocaching4locus.importgc.ImportUrlViewModel import com.arcao.geocaching4locus.live_map.LiveMapViewModel @@ -104,9 +100,9 @@ internal val appModule = module { // login viewModel { LoginViewModel(get(), get(), get(), get(), get(), get(), get()) } // dashboard - viewModel { (calledFromLocusMap: Boolean) -> + viewModel { parameters -> DashboardViewModel( - calledFromLocusMap, + parameters.get(), get(), get(), get(), @@ -135,9 +131,9 @@ internal val appModule = module { // import bookmarks viewModel { ImportBookmarkViewModel(get(), get(), get()) } viewModel { BookmarkListViewModel(get(), get(), get(), get(), get(), get(), get(), get()) } - viewModel { (bl: GeocacheListEntity) -> + viewModel { parameters -> BookmarkViewModel( - bl, + parameters.get(), get(), get(), get(), @@ -149,14 +145,12 @@ internal val appModule = module { get() ) } - factory { GeocacheUserListsDataSourceFactory(get(), get()) } - factory { ListGeocachesDataSourceFactory(get(), get()) } // live map factory { LiveMapViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get()) } // search nearest - viewModel { (intent: Intent) -> + viewModel { parameters -> SearchNearestViewModel( - intent, + parameters.get(), get(), get(), get(), diff --git a/app/src/main/java/com/arcao/geocaching4locus/authentication/LoginActivity.kt b/app/src/main/java/com/arcao/geocaching4locus/authentication/LoginActivity.kt index 61eb3a1b..9ae1146d 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/authentication/LoginActivity.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/authentication/LoginActivity.kt @@ -9,6 +9,7 @@ import android.os.Bundle import android.view.MenuItem import android.view.View import androidx.activity.addCallback +import androidx.activity.result.contract.ActivityResultContract import androidx.appcompat.widget.Toolbar import androidx.browser.customtabs.CustomTabsIntent import androidx.core.net.toUri @@ -35,6 +36,7 @@ class LoginActivity : AbstractActionBarActivity() { binding.vm = viewModel setContentView(binding.root) + @Suppress("USELESS_CAST") setSupportActionBar(binding.toolbar as Toolbar) @@ -102,7 +104,7 @@ class LoginActivity : AbstractActionBarActivity() { try { CustomTabsIntent.Builder() .setInstantAppsEnabled(true) - .enableUrlBarHiding() + .setUrlBarHidingEnabled(true) .build().launchUrl(this, url.toUri()) } catch (e: ActivityNotFoundException) { showWebPage(url.toUri()) @@ -137,4 +139,13 @@ class LoginActivity : AbstractActionBarActivity() { return intent } } + + object Contract : ActivityResultContract() { + override fun createIntent(context: Context, input: Void?) = + Intent(context, LoginActivity::class.java) + + override fun parseResult(resultCode: Int, intent: Intent?): Boolean { + return resultCode == Activity.RESULT_OK + } + } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/authentication/LoginViewModel.kt b/app/src/main/java/com/arcao/geocaching4locus/authentication/LoginViewModel.kt index 52dcaaee..a302c011 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/authentication/LoginViewModel.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/authentication/LoginViewModel.kt @@ -30,7 +30,7 @@ class LoginViewModel( val action = Command() private var job: Job? = null - val code = MutableLiveData("") + val code = MutableLiveData("") val continueButtonEnabled = Transformations.map(code, String::isNotBlank) val formVisible = MutableLiveData(true) var fromIntent = false diff --git a/app/src/main/java/com/arcao/geocaching4locus/authentication/util/PreferenceAccountManager.kt b/app/src/main/java/com/arcao/geocaching4locus/authentication/util/PreferenceAccountManager.kt index e555a2ef..82fb5eac 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/authentication/util/PreferenceAccountManager.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/authentication/util/PreferenceAccountManager.kt @@ -1,9 +1,7 @@ package com.arcao.geocaching4locus.authentication.util -import android.app.Activity import android.content.Context import androidx.core.content.edit -import com.arcao.geocaching4locus.authentication.LoginActivity import com.arcao.geocaching4locus.base.constants.PrefConstants import com.arcao.geocaching4locus.data.account.AccountManager import com.arcao.geocaching4locus.data.account.GeocachingAccount @@ -84,14 +82,6 @@ class PreferenceAccountManager(context: Context, oAuthService: OAuth20Service) : } } -fun AccountManager.requestSignOn(activity: Activity, requestCode: Int): Boolean { - if (account != null) - return false - - activity.startActivityForResult(LoginActivity.createIntent(activity), requestCode) - return true -} - val AccountManager.isPremium: Boolean get() = account?.isPremium() ?: false diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/constants/AppConstants.kt b/app/src/main/java/com/arcao/geocaching4locus/base/constants/AppConstants.kt index 6fa1b3a3..68d92847 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/constants/AppConstants.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/constants/AppConstants.kt @@ -1,12 +1,10 @@ package com.arcao.geocaching4locus.base.constants import android.net.Uri -import android.os.Build import android.util.Base64 import com.arcao.geocaching4locus.data.api.model.GeocacheSize import com.arcao.geocaching4locus.data.api.model.GeocacheType - -import locus.api.android.utils.LocusUtils +import locus.api.android.objects.VersionCode object AppConstants { const val OAUTH_CALLBACK_URL = "https://geocaching4locus.eu/oauth" @@ -27,16 +25,15 @@ object AppConstants { ) ) - const val LOCUS_MIN_VERSION = "3.36.0" - val LOCUS_MIN_VERSION_CODE: LocusUtils.VersionCode = LocusUtils.VersionCode.UPDATE_15 + const val LOCUS_MIN_VERSION = "3.52.0" + val LOCUS_MIN_VERSION_CODE: VersionCode = VersionCode.UPDATE_17 + + const val INITIAL_REQUEST_SIZE = 30 - /* Adaptive downloading configuration */ - const val ADAPTIVE_DOWNLOADING_MIN_ITEMS = 10 - const val ADAPTIVE_DOWNLOADING_MAX_ITEMS = 100 - const val ADAPTIVE_DOWNLOADING_STEP = 20 - const val ADAPTIVE_DOWNLOADING_MIN_TIME_MS = 3500 // more than time required for 30 calls per minute - const val ADAPTIVE_DOWNLOADING_MAX_TIME_MS = 10000 - const val ITEMS_PER_REQUEST = 30 + const val SEARCH_MAX_REQUEST_SIZE = 100 + const val LIVE_MAP_MAX_REQUEST_SIZE = SEARCH_MAX_REQUEST_SIZE + const val GEOCACHES_MAX_REQUEST_SIZE = 50 + const val LIST_GEOCACHES_DOWNLOAD_MAX_ITEMS = 50 const val MINUTES_PER_HOUR = 60 @@ -63,8 +60,7 @@ object AppConstants { const val DISTANCE_MIN_METERS = 100 const val DISTANCE_MAX_METERS = 50000 - val PREMIUM_CHARACTER = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) String(Character.toChars(0x1F451)) else "(PM)" + val PREMIUM_CHARACTER = String(Character.toChars(0x1F451)) const val NOTIFICATION_ID_LIVEMAP = 1 @@ -100,7 +96,7 @@ object AppConstants { GeocacheSize.NOT_CHOSEN, GeocacheSize.MICRO, GeocacheSize.SMALL, - GeocacheSize.MEDIUM, + GeocacheSize.REGULAR, GeocacheSize.LARGE, GeocacheSize.VIRTUAL, GeocacheSize.OTHER diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/fragment/AbstractDialogFragment.kt b/app/src/main/java/com/arcao/geocaching4locus/base/fragment/AbstractDialogFragment.kt index 5d2173e6..16b632e4 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/fragment/AbstractDialogFragment.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/fragment/AbstractDialogFragment.kt @@ -1,6 +1,5 @@ package com.arcao.geocaching4locus.base.fragment -import androidx.annotation.NonNull import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction @@ -16,7 +15,7 @@ abstract class AbstractDialogFragment : DialogFragment() { // This is work around for the situation when method show is called after saving // state even if you do all right. Especially when show is called after click on // a button. - override fun show(@NonNull transaction: FragmentTransaction, tag: String?): Int { + override fun show(transaction: FragmentTransaction, tag: String?): Int { return try { super.show(transaction, tag) } catch (e: IllegalStateException) { @@ -33,18 +32,6 @@ abstract class AbstractDialogFragment : DialogFragment() { } } - // This is to work around what is apparently a bug. If you don't have it - // here the dialog will be dismissed on rotation, so tell it not to dismiss. - override fun onDestroyView() { - dialog?.let { dialog -> - if (retainInstance) { - dialog.setDismissMessage(null) - } - } - - super.onDestroyView() - } - override fun dismiss() { try { super.dismiss() diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/fragment/AbstractPreferenceFragment.kt b/app/src/main/java/com/arcao/geocaching4locus/base/fragment/AbstractPreferenceFragment.kt index ffffcaeb..dda4392f 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/fragment/AbstractPreferenceFragment.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/fragment/AbstractPreferenceFragment.kt @@ -34,18 +34,18 @@ abstract class AbstractPreferenceFragment : PreferenceFragmentCompat(), override fun onCreate(paramBundle: Bundle?) { super.onCreate(paramBundle) - preferences = PreferenceManager.getDefaultSharedPreferences(activity) + preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) } override fun onResume() { super.onResume() - preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) + preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) preparePreference() } override fun onPause() { super.onPause() - preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) + preferenceScreen.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) } @CallSuper diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/fragment/SliderDialogFragment.kt b/app/src/main/java/com/arcao/geocaching4locus/base/fragment/SliderDialogFragment.kt index 0d417d21..9b65539b 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/fragment/SliderDialogFragment.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/fragment/SliderDialogFragment.kt @@ -15,8 +15,6 @@ import android.view.View import android.widget.EditText import android.widget.SeekBar import android.widget.TextView -import androidx.annotation.NonNull -import androidx.annotation.Nullable import androidx.annotation.StringRes import androidx.core.os.bundleOf import com.afollestad.materialdialogs.MaterialDialog @@ -62,7 +60,6 @@ class SliderDialogFragment : AbstractDialogFragment(), SeekBar.OnSeekBarChangeLi listener?.onDialogClosed(this) } - @NonNull override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val arguments = requireArguments() @@ -99,7 +96,7 @@ class SliderDialogFragment : AbstractDialogFragment(), SeekBar.OnSeekBarChangeLi } } - override fun onSaveInstanceState(@NonNull outState: Bundle) { + override fun onSaveInstanceState(outState: Bundle) { outState.putInt(PARAM_DEFAULT_VALUE, value) super.onSaveInstanceState(outState) } @@ -167,13 +164,12 @@ class SliderDialogFragment : AbstractDialogFragment(), SeekBar.OnSeekBarChangeLi seekBarView.progress = (value - minValue) / valueStep } - private class InputTextFilter internal constructor( - internal val editTextView: EditText, + private class InputTextFilter( + val editTextView: EditText, private val min: Int, private val max: Int, step: Int ) : NumberKeyListener() { - @Nullable private val availableValues: Array? init { @@ -195,19 +191,18 @@ class SliderDialogFragment : AbstractDialogFragment(), SeekBar.OnSeekBarChangeLi return InputType.TYPE_CLASS_TEXT } - @NonNull override fun getAcceptedChars(): CharArray { return DIGIT_CHARACTERS } override fun filter( - @NonNull source: CharSequence, + source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int - ): CharSequence? { + ): CharSequence { if (availableValues == null || availableValues.isEmpty()) { var filtered: CharSequence? = super.filter(source, start, end, dest, dstart, dend) if (filtered == null) { diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/paging/DataSourceState.kt b/app/src/main/java/com/arcao/geocaching4locus/base/paging/DataSourceState.kt deleted file mode 100644 index b4556948..00000000 --- a/app/src/main/java/com/arcao/geocaching4locus/base/paging/DataSourceState.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.arcao.geocaching4locus.base.paging - -sealed class DataSourceState { - object LoadingInitial : DataSourceState() - object LoadingNext : DataSourceState() - object Done : DataSourceState() - class Error(val e: Exception) : DataSourceState() -} diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/paging/PagingExt.kt b/app/src/main/java/com/arcao/geocaching4locus/base/paging/PagingExt.kt new file mode 100644 index 00000000..42ce49cc --- /dev/null +++ b/app/src/main/java/com/arcao/geocaching4locus/base/paging/PagingExt.kt @@ -0,0 +1,18 @@ +package com.arcao.geocaching4locus.base.paging + +import androidx.paging.CombinedLoadStates +import androidx.paging.LoadState + +fun CombinedLoadStates.handleErrors(callback: (Throwable) -> Unit) { + val state = source.append as? LoadState.Error + ?: source.prepend as? LoadState.Error + ?: source.refresh as? LoadState.Error + ?: append as? LoadState.Error + ?: prepend as? LoadState.Error + ?: refresh as? LoadState.Error + + if (state != null) { + callback(state.error) + } +} + diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/selection/SelectionTracker.kt b/app/src/main/java/com/arcao/geocaching4locus/base/selection/SelectionTracker.kt index 7db36f06..5af79936 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/selection/SelectionTracker.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/selection/SelectionTracker.kt @@ -5,7 +5,6 @@ import android.os.Parcelable import android.util.SparseArray import androidx.core.util.containsKey import androidx.core.util.forEach -import androidx.core.util.set import androidx.core.util.valueIterator import androidx.recyclerview.widget.RecyclerView import java.util.concurrent.CopyOnWriteArraySet diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GeocachingApiLoginUseCase.kt b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GeocachingApiLoginUseCase.kt index 3d5b7a11..2898032a 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GeocachingApiLoginUseCase.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GeocachingApiLoginUseCase.kt @@ -2,6 +2,7 @@ package com.arcao.geocaching4locus.base.usecase import androidx.annotation.WorkerThread import com.arcao.geocaching4locus.authentication.util.isAccountUpdateRequired +import com.arcao.geocaching4locus.authentication.util.restrictions import com.arcao.geocaching4locus.base.AccountNotFoundException import com.arcao.geocaching4locus.base.coroutine.CoroutinesDispatcherProvider import com.arcao.geocaching4locus.data.account.AccountManager @@ -20,7 +21,15 @@ class GeocachingApiLoginUseCase( val account = accountManager.account ?: throw AccountNotFoundException("Account not found.") if (account.isAccountUpdateRequired()) { - account.updateUserInfo(repository.user()) + val user = repository.user() + + account.updateUserInfo(user) + + // update restrictions + accountManager.restrictions().apply { + updateLimits(user) + applyRestrictions(user) + } } } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetGpsLocationUseCase.kt b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetGpsLocationUseCase.kt index 08028e52..a3b36623 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetGpsLocationUseCase.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetGpsLocationUseCase.kt @@ -23,8 +23,11 @@ class GetGpsLocationUseCase( @SuppressLint("MissingPermission") suspend operator fun invoke() = withContext(dispatcherProvider.main) { - suspendCancellableCoroutine { result -> - if (!context.hasGpsLocationPermission || !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { + suspendCancellableCoroutine { result -> + if (!context.hasGpsLocationPermission || !locationManager.isProviderEnabled( + LocationManager.GPS_PROVIDER + ) + ) { result.resume(null) return@suspendCancellableCoroutine } @@ -55,13 +58,14 @@ class GetGpsLocationUseCase( result.resume(location) } - override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { + @Deprecated("Deprecated in Java") + override fun onStatusChanged(provider: String, status: Int, extras: Bundle?) { } - override fun onProviderEnabled(provider: String?) { + override fun onProviderEnabled(provider: String) { } - override fun onProviderDisabled(provider: String?) { + override fun onProviderDisabled(provider: String) { if (result.isCompleted) return Timber.i("onProviderDisabled: $provider") diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetListGeocachesUseCase.kt b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetListGeocachesUseCase.kt index 04d09e53..6267c86d 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetListGeocachesUseCase.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetListGeocachesUseCase.kt @@ -55,10 +55,10 @@ class GetListGeocachesUseCase( ) = flow { geocachingApiLogin() - var count = AppConstants.ITEMS_PER_REQUEST + var count = AppConstants.INITIAL_REQUEST_SIZE var current = 0 - var itemsPerRequest = AppConstants.ITEMS_PER_REQUEST + var itemsPerRequest = AppConstants.INITIAL_REQUEST_SIZE while (current < count) { val startTimeMillis = System.currentTimeMillis() @@ -83,7 +83,11 @@ class GetListGeocachesUseCase( emit(mapper.createLocusPoints(geocaches)) current += geocaches.size - itemsPerRequest = DownloadingUtil.computeItemsPerRequest(itemsPerRequest, startTimeMillis) + itemsPerRequest = DownloadingUtil.computeRequestSize( + itemsPerRequest, + AppConstants.LIST_GEOCACHES_DOWNLOAD_MAX_ITEMS, + startTimeMillis + ) } Timber.v("found geocaches: %d", current) diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetLiveMapPointsFromRectangleCoordinatesUseCase.kt b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetLiveMapPointsFromRectangleCoordinatesUseCase.kt index eae5bed3..3cbddbe9 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetLiveMapPointsFromRectangleCoordinatesUseCase.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetLiveMapPointsFromRectangleCoordinatesUseCase.kt @@ -38,7 +38,6 @@ class GetLiveMapPointsFromRectangleCoordinatesUseCase( difficultyMax: Float = 5F, terrainMin: Float = 1F, terrainMax: Float = 5F, - excludeIgnoreList: Boolean = true, countHandler: (Int) -> Unit = {} ) = flow { geocachingApiLogin() @@ -82,8 +81,11 @@ class GetLiveMapPointsFromRectangleCoordinatesUseCase( emit(mapper.createLocusPoints(geocaches)) current += geocaches.size - itemsPerRequest = - DownloadingUtil.computeItemsPerRequest(itemsPerRequest, startTimeMillis) + itemsPerRequest = DownloadingUtil.computeRequestSize( + itemsPerRequest, + AppConstants.LIVE_MAP_MAX_REQUEST_SIZE, + startTimeMillis + ) } } finally { try { diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetOldPointNewPointPairFromPointUseCase.kt b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetOldPointNewPointPairFromPointUseCase.kt index 78cea97e..2cc7fd2c 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetOldPointNewPointPairFromPointUseCase.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetOldPointNewPointPairFromPointUseCase.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.yield import locus.api.mapper.DataMapper -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point class GetOldPointNewPointPairFromPointUseCase( private val repository: GeocachingApiRepository, @@ -29,8 +29,8 @@ class GetOldPointNewPointPairFromPointUseCase( ) = flow { geocachingApiLogin() - flow.takeListVariable(AppConstants.ITEMS_PER_REQUEST) { points -> - val requestedCacheIds = points.map { it.gcData.cacheID }.toTypedArray() + flow.takeListVariable(AppConstants.INITIAL_REQUEST_SIZE) { points -> + val requestedCacheIds = points.mapNotNull { it.gcData?.cacheID }.toTypedArray() val startTimeMillis = System.currentTimeMillis() @@ -47,12 +47,17 @@ class GetOldPointNewPointPairFromPointUseCase( if (cachesToAdd.isNotEmpty()) { val receivedPoints = mapper.createLocusPoints(cachesToAdd) for (oldPoint in points) { - val newPoint = receivedPoints.find { it.gcData.cacheID == oldPoint.gcData.cacheID } + val newPoint = + receivedPoints.find { it.gcData?.cacheID == oldPoint.gcData?.cacheID } emit(Pair(oldPoint, newPoint)) } } - DownloadingUtil.computeItemsPerRequest(points.size, startTimeMillis) + DownloadingUtil.computeRequestSize( + points.size, + AppConstants.GEOCACHES_MAX_REQUEST_SIZE, + startTimeMillis + ) } }.flowOn(dispatcherProvider.io) } diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromCoordinatesUseCase.kt b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromCoordinatesUseCase.kt index a84ec3e8..88ff4c53 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromCoordinatesUseCase.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromCoordinatesUseCase.kt @@ -39,7 +39,6 @@ class GetPointsFromCoordinatesUseCase( difficultyMax: Float = 5F, terrainMin: Float = 1F, terrainMax: Float = 5F, - excludeIgnoreList: Boolean = true, maxCount: Int = 50, countHandler: (Int) -> Unit = {} ) = flow { @@ -48,7 +47,7 @@ class GetPointsFromCoordinatesUseCase( var count = maxCount var current = 0 - var itemsPerRequest = AppConstants.ITEMS_PER_REQUEST + var itemsPerRequest = AppConstants.INITIAL_REQUEST_SIZE while (current < count) { val startTimeMillis = System.currentTimeMillis() @@ -89,7 +88,11 @@ class GetPointsFromCoordinatesUseCase( current += geocaches.size itemsPerRequest = - DownloadingUtil.computeItemsPerRequest(itemsPerRequest, startTimeMillis) + DownloadingUtil.computeRequestSize( + itemsPerRequest, + AppConstants.SEARCH_MAX_REQUEST_SIZE, + startTimeMillis + ) } Timber.v("found geocaches: %d", current) diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromGeocacheCodesUseCase.kt b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromGeocacheCodesUseCase.kt index 08674fa6..61457552 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromGeocacheCodesUseCase.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromGeocacheCodesUseCase.kt @@ -35,7 +35,7 @@ class GetPointsFromGeocacheCodesUseCase( val count = geocacheCodes.size var current = 0 - var itemsPerRequest = AppConstants.ITEMS_PER_REQUEST + var itemsPerRequest = AppConstants.INITIAL_REQUEST_SIZE while (current < count) { val startTimeMillis = System.currentTimeMillis() @@ -60,7 +60,11 @@ class GetPointsFromGeocacheCodesUseCase( current += requestedCacheIds.size - itemsPerRequest = DownloadingUtil.computeItemsPerRequest(itemsPerRequest, startTimeMillis) + itemsPerRequest = DownloadingUtil.computeRequestSize( + itemsPerRequest, + AppConstants.GEOCACHES_MAX_REQUEST_SIZE, + startTimeMillis + ) } Timber.v("found geocaches: %d", current) @@ -93,7 +97,11 @@ class GetPointsFromGeocacheCodesUseCase( } } - private fun getRequestedGeocacheIds(cacheIds: Array, current: Int, cachesPerRequest: Int): Array { + private fun getRequestedGeocacheIds( + cacheIds: Array, + current: Int, + cachesPerRequest: Int + ): Array { val count = min(cacheIds.size - current, cachesPerRequest) return cacheIds.copyOfRange(current, current + count) } diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromPointIndexesUseCase.kt b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromPointIndexesUseCase.kt index 8d7909b2..e941042d 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromPointIndexesUseCase.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromPointIndexesUseCase.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import locus.api.manager.LocusMapManager -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point class GetPointsFromPointIndexesUseCase( private val locusMapManager: LocusMapManager, diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromRectangleCoordinatesUseCase.kt b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromRectangleCoordinatesUseCase.kt index 55269d32..3bf8c554 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromRectangleCoordinatesUseCase.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetPointsFromRectangleCoordinatesUseCase.kt @@ -39,16 +39,15 @@ class GetPointsFromRectangleCoordinatesUseCase( difficultyMax: Float = 5F, terrainMin: Float = 1F, terrainMax: Float = 5F, - excludeIgnoreList: Boolean = true, maxCount: Int = 50, countHandler: (Int) -> Unit = {} ) = flow { geocachingApiLogin() - var count = AppConstants.ITEMS_PER_REQUEST + var count = maxCount var current = 0 - var itemsPerRequest = AppConstants.ITEMS_PER_REQUEST + var itemsPerRequest = AppConstants.INITIAL_REQUEST_SIZE while (current < count) { val startTimeMillis = System.currentTimeMillis() @@ -87,7 +86,11 @@ class GetPointsFromRectangleCoordinatesUseCase( current += geocaches.size itemsPerRequest = - DownloadingUtil.computeItemsPerRequest(itemsPerRequest, startTimeMillis) + DownloadingUtil.computeRequestSize( + itemsPerRequest, + AppConstants.SEARCH_MAX_REQUEST_SIZE, + startTimeMillis + ) } Timber.v("found geocaches: %d", current) diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetWifiLocationUseCase.kt b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetWifiLocationUseCase.kt index 48cc20b8..2bb44b6a 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetWifiLocationUseCase.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/GetWifiLocationUseCase.kt @@ -23,8 +23,11 @@ class GetWifiLocationUseCase( @SuppressLint("MissingPermission") suspend operator fun invoke() = withContext(dispatcherProvider.main) { - suspendCancellableCoroutine { result -> - if (!context.hasWifiLocationPermission || !locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { + suspendCancellableCoroutine { result -> + if (!context.hasWifiLocationPermission || !locationManager.isProviderEnabled( + LocationManager.NETWORK_PROVIDER + ) + ) { result.resume(null) return@suspendCancellableCoroutine } @@ -55,13 +58,14 @@ class GetWifiLocationUseCase( result.resume(location) } - override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { + @Deprecated("Deprecated in Java") + override fun onStatusChanged(provider: String, status: Int, extras: Bundle?) { } - override fun onProviderEnabled(provider: String?) { + override fun onProviderEnabled(provider: String) { } - override fun onProviderDisabled(provider: String?) { + override fun onProviderDisabled(provider: String) { if (result.isCompleted) return Timber.i("onProviderDisabled: $provider") diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/SendPointsSilentToLocusMapUseCase.kt b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/SendPointsSilentToLocusMapUseCase.kt index a9433496..ba6cc440 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/SendPointsSilentToLocusMapUseCase.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/SendPointsSilentToLocusMapUseCase.kt @@ -2,10 +2,9 @@ package com.arcao.geocaching4locus.base.usecase import com.arcao.geocaching4locus.base.coroutine.CoroutinesDispatcherProvider import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.withContext import locus.api.manager.LocusMapManager -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point class SendPointsSilentToLocusMapUseCase( private val locusMapManager: LocusMapManager, diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/WritePointToPackPointsFileUseCase.kt b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/WritePointToPackPointsFileUseCase.kt index b58f8c4a..2b003055 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/WritePointToPackPointsFileUseCase.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/WritePointToPackPointsFileUseCase.kt @@ -2,11 +2,10 @@ package com.arcao.geocaching4locus.base.usecase import com.arcao.geocaching4locus.base.coroutine.CoroutinesDispatcherProvider import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.withContext import locus.api.android.objects.PackPoints import locus.api.manager.LocusMapManager -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point import locus.api.utils.StoreableWriter @Suppress("BlockingMethodInNonBlockingContext") diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/entity/GeocacheListEntity.kt b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/entity/GeocacheListEntity.kt index eca32430..98291796 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/entity/GeocacheListEntity.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/entity/GeocacheListEntity.kt @@ -2,7 +2,7 @@ package com.arcao.geocaching4locus.base.usecase.entity import android.os.Parcelable import com.arcao.geocaching4locus.data.api.model.enums.GeocacheListType -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class GeocacheListEntity( diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/entity/ListGeocacheEntity.kt b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/entity/ListGeocacheEntity.kt index 6231ce46..7b73ba27 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/usecase/entity/ListGeocacheEntity.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/usecase/entity/ListGeocacheEntity.kt @@ -2,8 +2,8 @@ package com.arcao.geocaching4locus.base.usecase.entity import android.os.Parcelable import com.arcao.geocaching4locus.data.api.util.ReferenceCode -import kotlinx.android.parcel.IgnoredOnParcel -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize @Parcelize data class ListGeocacheEntity( diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/util/CoordinatesFormatter.kt b/app/src/main/java/com/arcao/geocaching4locus/base/util/CoordinatesFormatter.kt index 679d2de5..5494ef0f 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/util/CoordinatesFormatter.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/util/CoordinatesFormatter.kt @@ -54,7 +54,7 @@ object CoordinatesFormatter { var direction = 1.0 try { - ch = tmp[index].toUpperCase() + ch = tmp[index].uppercaseChar() if (ch == 'S' || ch == 'W' || ch == '-') { direction = -1.0 index++ diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/util/DownloadingUtil.kt b/app/src/main/java/com/arcao/geocaching4locus/base/util/DownloadingUtil.kt index e350e073..f83d30bf 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/util/DownloadingUtil.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/util/DownloadingUtil.kt @@ -1,25 +1,36 @@ package com.arcao.geocaching4locus.base.util -import com.arcao.geocaching4locus.base.constants.AppConstants import kotlin.math.max import kotlin.math.min object DownloadingUtil { - fun computeItemsPerRequest(currentItemsPerRequest: Int, startTimeMillis: Long): Int { - var itemsPerRequest = currentItemsPerRequest + /* Adaptive downloading configuration */ + const val MIN_REQUEST_SIZE = 10 + const val REQUEST_SIZE_INCREMENT = 20 + const val REQUEST_SIZE_DECREMENT = REQUEST_SIZE_INCREMENT + const val MIN_REQUEST_TIME_MS = + 3500 // more than time required for 30 calls per minute + const val MAX_REQUEST_TIME_MS = 10000 + + fun computeRequestSize( + currentRequestSize: Int, + maxRequestSize: Int, + startTimeMillis: Long + ): Int { + var requestSize = currentRequestSize val requestDuration = System.currentTimeMillis() - startTimeMillis - // keep the request time between ADAPTIVE_DOWNLOADING_MIN_TIME_MS and ADAPTIVE_DOWNLOADING_MAX_TIME_MS - if (requestDuration < AppConstants.ADAPTIVE_DOWNLOADING_MIN_TIME_MS) - itemsPerRequest += AppConstants.ADAPTIVE_DOWNLOADING_STEP + // keep the request time between MIN_REQUEST_TIME_MS and MAX_REQUEST_TIME_MS + if (requestDuration < MIN_REQUEST_TIME_MS) + requestSize += REQUEST_SIZE_INCREMENT - if (requestDuration > AppConstants.ADAPTIVE_DOWNLOADING_MAX_TIME_MS) - itemsPerRequest -= AppConstants.ADAPTIVE_DOWNLOADING_STEP + if (requestDuration > MAX_REQUEST_TIME_MS) + requestSize -= REQUEST_SIZE_DECREMENT // keep the value in a range - itemsPerRequest = max(itemsPerRequest, AppConstants.ADAPTIVE_DOWNLOADING_MIN_ITEMS) - itemsPerRequest = min(itemsPerRequest, AppConstants.ADAPTIVE_DOWNLOADING_MAX_ITEMS) + requestSize = max(requestSize, MIN_REQUEST_SIZE) + requestSize = min(requestSize, maxRequestSize) - return itemsPerRequest + return requestSize } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/util/FlowExtension.kt b/app/src/main/java/com/arcao/geocaching4locus/base/util/FlowExtension.kt index 5c553a42..c93c5c4d 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/util/FlowExtension.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/util/FlowExtension.kt @@ -1,7 +1,6 @@ package com.arcao.geocaching4locus.base.util import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect suspend fun Flow.takeListVariable(initialCount: Int, action: suspend (List) -> Int) { if (initialCount <= 0) { diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/util/IntentExtension.kt b/app/src/main/java/com/arcao/geocaching4locus/base/util/IntentExtension.kt index ebc54143..35c2344e 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/util/IntentExtension.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/util/IntentExtension.kt @@ -1,6 +1,7 @@ package com.arcao.geocaching4locus.base.util import android.app.Activity +import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -13,11 +14,11 @@ fun Activity.showWebPage(uri: Uri): Boolean { .addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK) - return if (intent.resolveActivity(packageManager) != null) { + return try { startActivity(intent) true - } else { - Toast.makeText(this, "Web page cannot be opened. No application found to show web pages.", Toast.LENGTH_LONG) + } catch (e: ActivityNotFoundException) { + Toast.makeText(this, "Unable to open web page, no application found.", Toast.LENGTH_LONG) .show() false } diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/util/LiveDataExtension.kt b/app/src/main/java/com/arcao/geocaching4locus/base/util/LiveDataExtension.kt index e4428371..ad4066cf 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/util/LiveDataExtension.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/util/LiveDataExtension.kt @@ -10,7 +10,7 @@ import java.util.concurrent.atomic.AtomicBoolean @MainThread inline fun LiveData.withObserve(owner: LifecycleOwner, crossinline block: (T) -> Unit) = - observe(owner, Observer { block(it!!) }) + observe(owner, { block(it!!) }) @MainThread operator fun MutableLiveData.invoke() { @@ -34,11 +34,11 @@ open class Command : MutableLiveData() { Timber.e("${javaClass.simpleName}: Multiple observers registered but only one will be notified of changes.") } - super.observe(owner, Observer { + super.observe(owner) { if (pending.compareAndSet(true, false)) { observer.onChanged(it) } - }) + } } @MainThread diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/util/LocusMapExtension.kt b/app/src/main/java/com/arcao/geocaching4locus/base/util/LocusMapExtension.kt index 504c7ef5..dcb4dd20 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/util/LocusMapExtension.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/util/LocusMapExtension.kt @@ -5,7 +5,7 @@ import androidx.fragment.app.FragmentActivity import com.arcao.geocaching4locus.error.fragment.LocusTestingErrorDialogFragment import locus.api.android.utils.IntentHelper import locus.api.android.utils.LocusConst -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point fun FragmentActivity.showLocusMissingError() = LocusTestingErrorDialogFragment.newInstance(this).show(supportFragmentManager) diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/util/PermissionUtil.kt b/app/src/main/java/com/arcao/geocaching4locus/base/util/PermissionUtil.kt index 9923d791..d5d96acc 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/util/PermissionUtil.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/util/PermissionUtil.kt @@ -3,9 +3,6 @@ package com.arcao.geocaching4locus.base.util import android.Manifest import android.content.Context import android.content.pm.PackageManager -import androidx.annotation.NonNull -import androidx.appcompat.app.AppCompatActivity -import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat object PermissionUtil { @@ -15,41 +12,17 @@ object PermissionUtil { ) val PERMISSION_LOCATION_WIFI = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION) - fun verifyPermissions(@NonNull grantResults: IntArray): Boolean { - for (result in grantResults) { - if (result != PackageManager.PERMISSION_GRANTED) { - return false - } - } - return true - } - fun hasPermission(context: Context, vararg permissions: String): Boolean { for (permission in permissions) { - if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED) - return false + if (ContextCompat.checkSelfPermission( + context, + permission + ) == PackageManager.PERMISSION_DENIED + ) return false } return true } - - fun requestGpsLocationPermission(activity: AppCompatActivity, requestCode: Int): Boolean { - return if (activity.hasGpsLocationPermission) { - true - } else { - ActivityCompat.requestPermissions(activity, PERMISSION_LOCATION_GPS, requestCode) - false - } - } - - fun requestWifiLocationPermission(activity: AppCompatActivity, requestCode: Int): Boolean { - return if (activity.hasGpsLocationPermission) { - true - } else { - ActivityCompat.requestPermissions(activity, PERMISSION_LOCATION_WIFI, requestCode) - false - } - } } val Context.hasGpsLocationPermission diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/util/ResourcesExtension.kt b/app/src/main/java/com/arcao/geocaching4locus/base/util/ResourcesExtension.kt index 8f882409..e7341331 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/util/ResourcesExtension.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/util/ResourcesExtension.kt @@ -23,7 +23,7 @@ fun Context.getMainLocale(): Locale { return if (locales.isEmpty) { Locale.getDefault() } else { - locales[0] + locales[0] ?: Locale.getDefault() } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/base/util/ServiceUtil.kt b/app/src/main/java/com/arcao/geocaching4locus/base/util/ServiceUtil.kt index 49bf636a..38f2ce6b 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/base/util/ServiceUtil.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/base/util/ServiceUtil.kt @@ -111,8 +111,8 @@ object ServiceUtil { } } - private class WakeLockContainer internal constructor( - internal val tag: String, - internal val wakeLock: PowerManager.WakeLock + private class WakeLockContainer( + val tag: String, + val wakeLock: PowerManager.WakeLock ) } diff --git a/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardActivity.kt b/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardActivity.kt index 8f57c4f1..90b1aae9 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardActivity.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardActivity.kt @@ -1,21 +1,19 @@ package com.arcao.geocaching4locus.dashboard -import android.app.Activity -import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem +import androidx.activity.result.ActivityResultCallback import androidx.appcompat.widget.Toolbar import androidx.databinding.DataBindingUtil import com.arcao.geocaching4locus.R -import com.arcao.geocaching4locus.authentication.util.requestSignOn +import com.arcao.geocaching4locus.authentication.LoginActivity import com.arcao.geocaching4locus.base.AbstractActionBarActivity import com.arcao.geocaching4locus.base.constants.AppConstants import com.arcao.geocaching4locus.base.util.isCalledFromLocusMap import com.arcao.geocaching4locus.base.util.showLocusMissingError import com.arcao.geocaching4locus.base.util.showWebPage import com.arcao.geocaching4locus.base.util.withObserve -import com.arcao.geocaching4locus.data.account.AccountManager import com.arcao.geocaching4locus.databinding.ActivityDashboardBinding import com.arcao.geocaching4locus.download_rectangle.DownloadRectangleActivity import com.arcao.geocaching4locus.import_bookmarks.ImportBookmarkActivity @@ -23,7 +21,6 @@ import com.arcao.geocaching4locus.importgc.ImportGeocacheCodeActivity import com.arcao.geocaching4locus.live_map.fragment.PowerSaveWarningDialogFragment import com.arcao.geocaching4locus.search_nearest.SearchNearestActivity import com.arcao.geocaching4locus.settings.SettingsActivity -import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -34,10 +31,24 @@ class DashboardActivity : AbstractActionBarActivity(), parametersOf(isCalledFromLocusMap()) } - private val accountManager by inject() - private lateinit var binding: ActivityDashboardBinding + private val successCallback = ActivityResultCallback { success -> + if (success) finish() + } + private val searchNearestActivity = + registerForActivityResult(SearchNearestActivity.Contract, successCallback) + private val importGeocacheCodeActivity = + registerForActivityResult(ImportGeocacheCodeActivity.Contract, successCallback) + private val downloadRectangleActivity = + registerForActivityResult(DownloadRectangleActivity.Contract, successCallback) + private val importBookmarkActivity = + registerForActivityResult(ImportBookmarkActivity.Contract, successCallback) + private val settingsActivity = registerForActivityResult(SettingsActivity.Contract) {} + private val loginActivity = registerForActivityResult(LoginActivity.Contract) { success -> + if (success) viewModel.onClickLiveMap() + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -47,39 +58,26 @@ class DashboardActivity : AbstractActionBarActivity(), viewModel.action.withObserve(this, ::handleAction) + @Suppress("USELESS_CAST") setSupportActionBar(binding.toolbar as Toolbar) supportActionBar?.title = title } - private fun handleAction(action: DashboardAction?) { + private fun handleAction(action: DashboardAction) { when (action) { - is DashboardAction.SearchNearest -> { - startActivityForResult( - Intent(this, SearchNearestActivity::class.java).apply { - if (isCalledFromLocusMap()) putExtras(intent) - }, 0 - ) - } - is DashboardAction.ImportGcCode -> - startActivityForResult(Intent(this, ImportGeocacheCodeActivity::class.java), 0) - is DashboardAction.DownloadLiveMapGeocaches -> - startActivityForResult(Intent(this, DownloadRectangleActivity::class.java), 0) - is DashboardAction.ImportBookmarks -> - startActivityForResult(Intent(this, ImportBookmarkActivity::class.java), 0) - is DashboardAction.Preferences -> - startActivity(SettingsActivity.createIntent(this)) - is DashboardAction.UsersGuide -> - showWebPage(AppConstants.USERS_GUIDE_URI) - - is DashboardAction.LocusMapNotInstalled -> - showLocusMissingError() - is DashboardAction.SignIn -> - accountManager.requestSignOn(this, REQUEST_SIGN_ON) - is DashboardAction.WarnPowerSaveActive -> - PowerSaveWarningDialogFragment.newInstance().show(supportFragmentManager) - - is DashboardAction.NavigationBack -> - finish() + is DashboardAction.SearchNearest -> searchNearestActivity.launch( + if (isCalledFromLocusMap()) intent else null + ) + is DashboardAction.ImportGcCode -> importGeocacheCodeActivity.launch(null) + is DashboardAction.DownloadLiveMapGeocaches -> downloadRectangleActivity.launch(null) + is DashboardAction.ImportBookmarks -> importBookmarkActivity.launch(null) + is DashboardAction.Preferences -> settingsActivity.launch(null) + is DashboardAction.UsersGuide -> showWebPage(AppConstants.USERS_GUIDE_URI) + is DashboardAction.LocusMapNotInstalled -> showLocusMissingError() + is DashboardAction.SignIn -> loginActivity.launch(null) + is DashboardAction.WarnPowerSaveActive -> PowerSaveWarningDialogFragment.newInstance() + .show(supportFragmentManager) + is DashboardAction.NavigationBack -> finish() } } @@ -102,27 +100,7 @@ class DashboardActivity : AbstractActionBarActivity(), } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - if (requestCode == REQUEST_SIGN_ON && resultCode == Activity.RESULT_OK) { - viewModel.onClickLiveMap() - } else if (resultCode == Activity.RESULT_OK) { - finish() - } - } - - override fun onResume() { - super.onResume() - - binding.vm = viewModel - } - override fun onPowerSaveWarningConfirmed() { viewModel.onPowerSaveWarningConfirmed() } - - companion object { - private const val REQUEST_SIGN_ON = 1 - } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardViewModel.kt b/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardViewModel.kt index 7c7cfe79..2455febc 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardViewModel.kt @@ -1,6 +1,6 @@ package com.arcao.geocaching4locus.dashboard -import android.content.Context +import android.app.Application import androidx.annotation.UiThread import androidx.lifecycle.MutableLiveData import com.arcao.geocaching4locus.authentication.util.isPremium @@ -16,7 +16,7 @@ import locus.api.manager.LocusMapManager class DashboardViewModel( private val calledFromLocusMap: Boolean, - private val context: Context, + private val context: Application, private val notificationManager: LiveMapNotificationManager, private val accountManager: AccountManager, private val locusMapManager: LocusMapManager, diff --git a/app/src/main/java/com/arcao/geocaching4locus/dashboard/widget/DashboardButton.kt b/app/src/main/java/com/arcao/geocaching4locus/dashboard/widget/DashboardButton.kt index f8293f37..121f634d 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/dashboard/widget/DashboardButton.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/dashboard/widget/DashboardButton.kt @@ -4,9 +4,9 @@ import android.content.Context import android.content.res.ColorStateList import android.content.res.TypedArray import android.util.AttributeSet -import android.widget.ToggleButton import androidx.annotation.StyleableRes import androidx.appcompat.content.res.AppCompatResources +import androidx.appcompat.widget.AppCompatToggleButton import androidx.core.content.withStyledAttributes import androidx.core.graphics.drawable.DrawableCompat import com.arcao.geocaching4locus.R @@ -15,7 +15,7 @@ class DashboardButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = R.attr.dashboardButtonStyle -) : ToggleButton( +) : AppCompatToggleButton( context, attrs, defStyleAttr @@ -45,7 +45,11 @@ class DashboardButton @JvmOverloads constructor( private fun applyCompoundDrawableTint(a: TypedArray) { // support for alpha attribute in ColorStateList - getCompatColorStateList(context, a, R.styleable.DashboardButton_compoundDrawableTint)?.let { colorList -> + getCompatColorStateList( + context, + a, + R.styleable.DashboardButton_compoundDrawableTint + )?.let { colorList -> val compoundDrawables = compoundDrawables for (i in 0..3) { if (compoundDrawables[i] == null) @@ -54,7 +58,12 @@ class DashboardButton @JvmOverloads constructor( compoundDrawables[i] = DrawableCompat.wrap(compoundDrawables[i]) DrawableCompat.setTintList(compoundDrawables[i], colorList) } - setCompoundDrawables(compoundDrawables[0], compoundDrawables[1], compoundDrawables[2], compoundDrawables[3]) + setCompoundDrawables( + compoundDrawables[0], + compoundDrawables[1], + compoundDrawables[2], + compoundDrawables[3] + ) } } @@ -68,8 +77,15 @@ class DashboardButton @JvmOverloads constructor( super.setChecked(checked) } - private fun getCompatColorStateList(context: Context, a: TypedArray, @StyleableRes index: Int): ColorStateList? { + private fun getCompatColorStateList( + context: Context, + a: TypedArray, + @StyleableRes index: Int + ): ColorStateList? { val resourceId = a.getResourceId(index, 0) - return if (resourceId == 0) null else AppCompatResources.getColorStateList(context, resourceId) + return if (resourceId == 0) null else AppCompatResources.getColorStateList( + context, + resourceId + ) } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/download_rectangle/DownloadRectangleActivity.kt b/app/src/main/java/com/arcao/geocaching4locus/download_rectangle/DownloadRectangleActivity.kt index b1577e90..55d45855 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/download_rectangle/DownloadRectangleActivity.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/download_rectangle/DownloadRectangleActivity.kt @@ -1,22 +1,30 @@ package com.arcao.geocaching4locus.download_rectangle import android.app.Activity +import android.content.Context import android.content.Intent import android.os.Bundle +import androidx.activity.result.contract.ActivityResultContract import com.arcao.geocaching4locus.R -import com.arcao.geocaching4locus.authentication.util.requestSignOn +import com.arcao.geocaching4locus.authentication.LoginActivity import com.arcao.geocaching4locus.base.AbstractActionBarActivity import com.arcao.geocaching4locus.base.util.exhaustive import com.arcao.geocaching4locus.base.util.showLocusMissingError import com.arcao.geocaching4locus.base.util.withObserve -import com.arcao.geocaching4locus.data.account.AccountManager import com.arcao.geocaching4locus.error.ErrorActivity -import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel class DownloadRectangleActivity : AbstractActionBarActivity() { val viewModel by viewModel() - private val accountManager by inject() + + private val loginActivity = registerForActivityResult(LoginActivity.Contract) { success -> + if (success) { + viewModel.startDownload() + } else { + setResult(Activity.RESULT_CANCELED) + finish() + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -54,31 +62,23 @@ class DownloadRectangleActivity : AbstractActionBarActivity() { setResult(Activity.RESULT_CANCELED) finish() } - is DownloadRectangleAction.SignIn -> { - accountManager.requestSignOn(this, REQUEST_SIGN_ON) - } + is DownloadRectangleAction.SignIn -> loginActivity.launch(null) DownloadRectangleAction.LastLiveMapDataInvalid -> { - startActivity(ErrorActivity.IntentBuilder(this).message(R.string.error_live_map_geocaches_not_visible).build()) + startActivity( + ErrorActivity.IntentBuilder(this) + .message(R.string.error_live_map_geocaches_not_visible).build() + ) finish() } }.exhaustive } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) + object Contract : ActivityResultContract() { + override fun createIntent(context: Context, input: Void?) = + Intent(context, DownloadRectangleActivity::class.java) - // restart update process after log in - if (requestCode == REQUEST_SIGN_ON) { - if (resultCode == Activity.RESULT_OK) { - viewModel.startDownload() - } else { - setResult(Activity.RESULT_CANCELED) - finish() - } + override fun parseResult(resultCode: Int, intent: Intent?): Boolean { + return resultCode == Activity.RESULT_OK } } - - companion object { - private const val REQUEST_SIGN_ON = 1 - } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/download_rectangle/DownloadRectangleViewModel.kt b/app/src/main/java/com/arcao/geocaching4locus/download_rectangle/DownloadRectangleViewModel.kt index d23da1ed..38fef206 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/download_rectangle/DownloadRectangleViewModel.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/download_rectangle/DownloadRectangleViewModel.kt @@ -1,6 +1,6 @@ package com.arcao.geocaching4locus.download_rectangle -import android.content.Context +import android.app.Application import com.arcao.geocaching4locus.R import com.arcao.geocaching4locus.base.BaseViewModel import com.arcao.geocaching4locus.base.constants.AppConstants @@ -21,7 +21,7 @@ import locus.api.manager.LocusMapManager import timber.log.Timber class DownloadRectangleViewModel constructor( - private val context: Context, + private val context: Application, private val accountManager: AccountManager, private val exceptionHandler: ExceptionHandler, private val getPointsFromRectangleCoordinates: GetPointsFromRectangleCoordinatesUseCase, @@ -74,7 +74,7 @@ class DownloadRectangleViewModel constructor( center = true ) - var count = AppConstants.ITEMS_PER_REQUEST + var count = AppConstants.INITIAL_REQUEST_SIZE var receivedGeocaches = 0 try { @@ -94,7 +94,6 @@ class DownloadRectangleViewModel constructor( filterPreferenceManager.difficultyMax, filterPreferenceManager.terrainMin, filterPreferenceManager.terrainMax, - filterPreferenceManager.excludeIgnoreList, AppConstants.LIVEMAP_CACHES_COUNT ) { count = it }.map { list -> receivedGeocaches += list.size @@ -103,12 +102,14 @@ class DownloadRectangleViewModel constructor( // apply additional downloading full geocache if required if (filterPreferenceManager.simpleCacheData) { list.forEach { point -> - point.setExtraOnDisplay( - context.packageName, - UpdateActivity::class.java.name, - UpdateActivity.PARAM_SIMPLE_CACHE_ID, - point.gcData.cacheID - ) + point.gcData?.cacheID?.let { cacheId -> + point.setExtraOnDisplay( + context.packageName, + UpdateActivity::class.java.name, + UpdateActivity.PARAM_SIMPLE_CACHE_ID, + cacheId + ) + } } } list diff --git a/app/src/main/java/com/arcao/geocaching4locus/error/handler/ExceptionHandler.kt b/app/src/main/java/com/arcao/geocaching4locus/error/handler/ExceptionHandler.kt index 35bfd1ff..e4136aca 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/error/handler/ExceptionHandler.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/error/handler/ExceptionHandler.kt @@ -80,7 +80,10 @@ class ExceptionHandler(private val context: Context, private val accountManager: accountManager.deleteAccount() return builder.message(R.string.error_no_account) .positiveAction( - SettingsActivity.createIntent(context, AccountsPreferenceFragment::class.java) + SettingsActivity.Contract.createIntent( + context, + AccountsPreferenceFragment::class.java + ) ) .positiveButtonText(R.string.button_ok) .clearNegativeButtonText() diff --git a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/ImportBookmarkActivity.kt b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/ImportBookmarkActivity.kt index 0c0e30f4..e8057aec 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/ImportBookmarkActivity.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/ImportBookmarkActivity.kt @@ -1,27 +1,34 @@ package com.arcao.geocaching4locus.import_bookmarks import android.app.Activity +import android.content.Context import android.content.Intent import android.os.Bundle import android.view.MenuItem +import androidx.activity.result.contract.ActivityResultContract import androidx.fragment.app.commit import com.arcao.geocaching4locus.R -import com.arcao.geocaching4locus.authentication.util.requestSignOn +import com.arcao.geocaching4locus.authentication.LoginActivity import com.arcao.geocaching4locus.base.AbstractActionBarActivity import com.arcao.geocaching4locus.base.util.exhaustive import com.arcao.geocaching4locus.base.util.showLocusMissingError import com.arcao.geocaching4locus.base.util.withObserve -import com.arcao.geocaching4locus.data.account.AccountManager import com.arcao.geocaching4locus.error.ErrorActivity import com.arcao.geocaching4locus.import_bookmarks.fragment.BaseBookmarkFragment import com.arcao.geocaching4locus.import_bookmarks.fragment.BookmarkFragment import com.arcao.geocaching4locus.import_bookmarks.fragment.BookmarkListFragment -import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel class ImportBookmarkActivity : AbstractActionBarActivity() { private val viewModel by viewModel() - private val accountManager by inject() + + private val loginActivity = registerForActivityResult(LoginActivity.Contract) { success -> + if (success) { + viewModel.init() + } else { + finish() + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -52,12 +59,13 @@ class ImportBookmarkActivity : AbstractActionBarActivity() { } } ImportBookmarkAction.PremiumMembershipRequired -> { - startActivity(ErrorActivity.IntentBuilder(this).message(R.string.error_premium_feature).build()) + startActivity( + ErrorActivity.IntentBuilder(this).message(R.string.error_premium_feature) + .build() + ) finish() } - is ImportBookmarkAction.SignIn -> { - accountManager.requestSignOn(this, REQUEST_SIGN_ON) - } + is ImportBookmarkAction.SignIn -> loginActivity.launch(null) }.exhaustive } @@ -69,7 +77,9 @@ class ImportBookmarkActivity : AbstractActionBarActivity() { } override fun onProgressCancel(requestId: Int) { - (supportFragmentManager.findFragmentById(R.id.fragment) as? BaseBookmarkFragment)?.onProgressCancel(requestId) + (supportFragmentManager.findFragmentById(R.id.fragment) as? BaseBookmarkFragment)?.onProgressCancel( + requestId + ) } override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { @@ -80,20 +90,12 @@ class ImportBookmarkActivity : AbstractActionBarActivity() { else -> super.onOptionsItemSelected(item) } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) + object Contract : ActivityResultContract() { + override fun createIntent(context: Context, input: Void?) = + Intent(context, ImportBookmarkActivity::class.java) - // restart update process after log in - if (requestCode == REQUEST_SIGN_ON) { - if (resultCode == Activity.RESULT_OK) { - viewModel.init() - } else { - finish() - } + override fun parseResult(resultCode: Int, intent: Intent?): Boolean { + return resultCode == Activity.RESULT_OK } } - - companion object { - private const val REQUEST_SIGN_ON = 1 - } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/adapter/BookmarkGeocachesAdapter.kt b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/adapter/BookmarkGeocachesAdapter.kt index c20999bd..bfbe45fe 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/adapter/BookmarkGeocachesAdapter.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/adapter/BookmarkGeocachesAdapter.kt @@ -3,7 +3,7 @@ package com.arcao.geocaching4locus.import_bookmarks.adapter import android.view.LayoutInflater import android.view.ViewGroup import androidx.databinding.DataBindingUtil -import androidx.paging.PagedListAdapter +import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.arcao.geocaching4locus.R @@ -13,24 +13,22 @@ import com.arcao.geocaching4locus.base.usecase.entity.ListGeocacheEntity import com.arcao.geocaching4locus.databinding.ViewBookmarkItemBinding class BookmarkGeocachesAdapter : - PagedListAdapter(DiffCallback) { - init { - setHasStableIds(true) - } + PagingDataAdapter(DiffCallback) { val tracker by lazy { - SelectionTracker(object : SelectionAdapter { + val selectionAdapter = object : SelectionAdapter { override val itemCount: Int - get() = currentList?.loadedCount ?: 0 + get() = snapshot().size override fun registerAdapterDataObserver(adapterDataObserver: RecyclerView.AdapterDataObserver) = this@BookmarkGeocachesAdapter.registerAdapterDataObserver(adapterDataObserver) - override fun findPosition(value: ListGeocacheEntity): Int = - currentList?.indexOf(value) ?: RecyclerView.NO_POSITION + override fun findPosition(value: ListGeocacheEntity): Int = snapshot().indexOf(value) + + override fun getItem(position: Int) = snapshot()[position] + } - override fun getItem(position: Int) = currentList?.get(position) - }).apply { + SelectionTracker(selectionAdapter).apply { addSelectionChangeListener { startPosition: Int, count: Int -> notifyItemRangeChanged(startPosition, count) } @@ -40,11 +38,16 @@ class BookmarkGeocachesAdapter : val selected: List get() = tracker.selectedValues - override fun getItemId(position: Int) = getItem(position)?.id ?: RecyclerView.NO_ID - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(parent.context) - return ViewHolder(DataBindingUtil.inflate(inflater, R.layout.view_bookmark_item, parent, false)) + return ViewHolder( + DataBindingUtil.inflate( + inflater, + R.layout.view_bookmark_item, + parent, + false + ) + ) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { @@ -67,11 +70,17 @@ class BookmarkGeocachesAdapter : class ViewHolder(val binding: ViewBookmarkItemBinding) : RecyclerView.ViewHolder(binding.root) private object DiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: ListGeocacheEntity, newItem: ListGeocacheEntity): Boolean { + override fun areItemsTheSame( + oldItem: ListGeocacheEntity, + newItem: ListGeocacheEntity + ): Boolean { return oldItem.referenceCode == newItem.referenceCode } - override fun areContentsTheSame(oldItem: ListGeocacheEntity, newItem: ListGeocacheEntity): Boolean { + override fun areContentsTheSame( + oldItem: ListGeocacheEntity, + newItem: ListGeocacheEntity + ): Boolean { return oldItem == newItem } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/adapter/BookmarkListAdapter.kt b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/adapter/BookmarkListAdapter.kt index 3dd891ac..daa781a3 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/adapter/BookmarkListAdapter.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/adapter/BookmarkListAdapter.kt @@ -3,7 +3,7 @@ package com.arcao.geocaching4locus.import_bookmarks.adapter import android.view.LayoutInflater import android.view.ViewGroup import androidx.databinding.DataBindingUtil -import androidx.paging.PagedListAdapter +import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.arcao.geocaching4locus.R @@ -12,17 +12,18 @@ import com.arcao.geocaching4locus.databinding.ViewBookmarkListItemBinding class BookmarkListAdapter( private val onClickListener: (geocacheList: GeocacheListEntity, importAll: Boolean) -> Unit -) : PagedListAdapter(DiffCallback) { - - init { - setHasStableIds(true) - } - - override fun getItemId(position: Int) = getItem(position)?.id ?: RecyclerView.NO_ID +) : PagingDataAdapter(DiffCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(parent.context) - return ViewHolder(DataBindingUtil.inflate(inflater, R.layout.view_bookmark_list_item, parent, false)) + return ViewHolder( + DataBindingUtil.inflate( + inflater, + R.layout.view_bookmark_list_item, + parent, + false + ) + ) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { diff --git a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkFragment.kt b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkFragment.kt index 16be9fe7..6da9d958 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkFragment.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkFragment.kt @@ -10,10 +10,13 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf -import androidx.databinding.DataBindingUtil +import androidx.core.view.MenuProvider +import androidx.lifecycle.lifecycleScope +import androidx.paging.LoadState import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.arcao.geocaching4locus.R +import com.arcao.geocaching4locus.base.paging.handleErrors import com.arcao.geocaching4locus.base.usecase.entity.GeocacheListEntity import com.arcao.geocaching4locus.base.util.exhaustive import com.arcao.geocaching4locus.base.util.invoke @@ -22,6 +25,8 @@ import com.arcao.geocaching4locus.databinding.FragmentBookmarkBinding import com.arcao.geocaching4locus.error.hasPositiveAction import com.arcao.geocaching4locus.import_bookmarks.ImportBookmarkViewModel import com.arcao.geocaching4locus.import_bookmarks.adapter.BookmarkGeocachesAdapter +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -38,24 +43,40 @@ class BookmarkFragment : BaseBookmarkFragment() { private val adapter = BookmarkGeocachesAdapter() private val toolbar get() = (activity as? AppCompatActivity)?.supportActionBar - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + private val menuProvider = object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.toolbar_select_deselect, menu) + } - setHasOptionsMenu(true) + override fun onMenuItemSelected(menuItem: MenuItem) = when (menuItem.itemId) { + R.id.selectAll -> { + adapter.selectAll() + true + } + R.id.deselectAll -> { + adapter.selectNone() + true + } + else -> false + } } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { toolbar?.subtitle = bookmarkList.name - val binding = DataBindingUtil.inflate( + val binding = FragmentBookmarkBinding.inflate( inflater, - R.layout.fragment_bookmark, container, false ) binding.lifecycleOwner = viewLifecycleOwner binding.vm = viewModel + binding.isLoading = true binding.list.apply { adapter = this@BookmarkFragment.adapter layoutManager = LinearLayoutManager(context) @@ -65,9 +86,23 @@ class BookmarkFragment : BaseBookmarkFragment() { viewModel.selection(adapter.selected) } - viewModel.list.withObserve(viewLifecycleOwner) { list -> - adapter.submitList(list) - adapter.tracker.onRestoreInstanceState(savedInstanceState) + var savedState = savedInstanceState + + viewLifecycleOwner.lifecycleScope.launch { + viewModel.pagerFlow.collectLatest { data -> + adapter.submitData(data) + + if (savedState != null) { + adapter.tracker.onRestoreInstanceState(savedState) + savedState = null + } + } + adapter.loadStateFlow.collect { state -> + val isListEmpty = state.refresh is LoadState.NotLoading && adapter.itemCount == 0 + binding.isEmpty = isListEmpty + binding.isLoading = state.source.refresh is LoadState.Loading + state.handleErrors(viewModel::handleLoadError) + } } viewModel.action.withObserve(viewLifecycleOwner, ::handleAction) @@ -78,25 +113,15 @@ class BookmarkFragment : BaseBookmarkFragment() { return binding.root } - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - adapter.tracker.onSaveInstanceState(outState) - } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.toolbar_select_deselect, menu) + requireActivity().addMenuProvider(menuProvider, viewLifecycleOwner) } - override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { - R.id.selectAll -> { - adapter.selectAll() - true - } - R.id.deselectAll -> { - adapter.selectNone() - true - } - else -> super.onOptionsItemSelected(item) + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + adapter.tracker.onSaveInstanceState(outState) } @Suppress("IMPLICIT_CAST_TO_ANY") @@ -106,10 +131,11 @@ class BookmarkFragment : BaseBookmarkFragment() { startActivity(action.intent) requireActivity().apply { setResult( - if (intent.hasPositiveAction()) + if (action.intent.hasPositiveAction()) { Activity.RESULT_OK - else + } else { Activity.RESULT_CANCELED + } ) finish() } diff --git a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkListFragment.kt b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkListFragment.kt index 8dae428d..36fb7d6c 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkListFragment.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkListFragment.kt @@ -8,8 +8,11 @@ import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf import androidx.databinding.DataBindingUtil +import androidx.lifecycle.lifecycleScope +import androidx.paging.LoadState import androidx.recyclerview.widget.LinearLayoutManager import com.arcao.geocaching4locus.R +import com.arcao.geocaching4locus.base.paging.handleErrors import com.arcao.geocaching4locus.base.util.exhaustive import com.arcao.geocaching4locus.base.util.invoke import com.arcao.geocaching4locus.base.util.withObserve @@ -18,6 +21,8 @@ import com.arcao.geocaching4locus.error.hasPositiveAction import com.arcao.geocaching4locus.import_bookmarks.ImportBookmarkViewModel import com.arcao.geocaching4locus.import_bookmarks.adapter.BookmarkListAdapter import com.arcao.geocaching4locus.import_bookmarks.widget.decorator.MarginItemDecoration +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.viewModel @@ -34,7 +39,11 @@ class BookmarkListFragment : BaseBookmarkFragment() { } } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { toolbar?.subtitle = null val binding = DataBindingUtil.inflate( @@ -46,13 +55,24 @@ class BookmarkListFragment : BaseBookmarkFragment() { binding.lifecycleOwner = viewLifecycleOwner binding.vm = viewModel + binding.isLoading = true binding.list.apply { adapter = this@BookmarkListFragment.adapter layoutManager = LinearLayoutManager(context) addItemDecoration(MarginItemDecoration(context, R.dimen.cardview_space)) } - viewModel.list.withObserve(viewLifecycleOwner, adapter::submitList) + viewLifecycleOwner.lifecycleScope.launch { + viewModel.pagerFlow.collectLatest { data -> + adapter.submitData(data) + } + adapter.loadStateFlow.collect { state -> + val isListEmpty = state.refresh is LoadState.NotLoading && adapter.itemCount == 0 + binding.isEmpty = isListEmpty + binding.isLoading = state.source.refresh is LoadState.Loading + state.handleErrors(viewModel::handleLoadError) + } + } viewModel.action.withObserve(viewLifecycleOwner, ::handleAction) viewModel.progress.withObserve(viewLifecycleOwner) { state -> @@ -69,10 +89,11 @@ class BookmarkListFragment : BaseBookmarkFragment() { startActivity(action.intent) requireActivity().apply { setResult( - if (intent.hasPositiveAction()) + if (action.intent.hasPositiveAction()) { Activity.RESULT_OK - else + } else { Activity.RESULT_CANCELED + } ) finish() } diff --git a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkListViewModel.kt b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkListViewModel.kt index f4618837..7ba5dd65 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkListViewModel.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkListViewModel.kt @@ -1,16 +1,16 @@ package com.arcao.geocaching4locus.import_bookmarks.fragment -import android.content.Context -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Transformations -import androidx.paging.LivePagedListBuilder -import androidx.paging.PagedList +import android.app.Application +import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn import com.arcao.geocaching4locus.R import com.arcao.geocaching4locus.base.BaseViewModel import com.arcao.geocaching4locus.base.coroutine.CoroutinesDispatcherProvider -import com.arcao.geocaching4locus.base.paging.DataSourceState import com.arcao.geocaching4locus.base.usecase.GetListGeocachesUseCase +import com.arcao.geocaching4locus.base.usecase.GetUserListsUseCase import com.arcao.geocaching4locus.base.usecase.WritePointToPackPointsFileUseCase import com.arcao.geocaching4locus.base.usecase.entity.GeocacheListEntity import com.arcao.geocaching4locus.base.util.Command @@ -18,58 +18,41 @@ import com.arcao.geocaching4locus.base.util.invoke import com.arcao.geocaching4locus.error.exception.IntendedException import com.arcao.geocaching4locus.error.handler.ExceptionHandler import com.arcao.geocaching4locus.import_bookmarks.paging.GeocacheUserListsDataSource -import com.arcao.geocaching4locus.import_bookmarks.paging.GeocacheUserListsDataSourceFactory import com.arcao.geocaching4locus.settings.manager.FilterPreferenceManager import com.arcao.geocaching4locus.update.UpdateActivity import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import locus.api.manager.LocusMapManager import timber.log.Timber @Suppress("EXPERIMENTAL_API_USAGE") class BookmarkListViewModel( - private val context: Context, + private val context: Application, private val exceptionHandler: ExceptionHandler, - private val dataSourceFactory: GeocacheUserListsDataSourceFactory, + private val getUserLists: GetUserListsUseCase, private val getListGeocaches: GetListGeocachesUseCase, private val writePointToPackPointsFile: WritePointToPackPointsFileUseCase, private val filterPreferenceManager: FilterPreferenceManager, private val locusMapManager: LocusMapManager, dispatcherProvider: CoroutinesDispatcherProvider ) : BaseViewModel(dispatcherProvider) { - val loading = MutableLiveData() - val list: LiveData> + val pagerFlow: Flow> val action = Command() private var job: Job? = null - val state: LiveData - get() = Transformations.switchMap( - dataSourceFactory.dataSource, - GeocacheUserListsDataSource::state - ) - init { val pageSize = 25 - val config = PagedList.Config.Builder() - .setPageSize(pageSize) - .setInitialLoadSizeHint(pageSize * 2) - .setEnablePlaceholders(false) - .build() - - list = LivePagedListBuilder(dataSourceFactory, config).build() + val config = PagingConfig( + pageSize = pageSize, + enablePlaceholders = false, + initialLoadSize = 2 * pageSize + ) - state.observeForever { state -> - if (state == DataSourceState.LoadingInitial) { - loading(true) - } else { - if (loading.value == true) { - loading(false) - } - } - if (state is DataSourceState.Error) { - action(BookmarkListAction.LoadingError(exceptionHandler(state.e))) - } - } + pagerFlow = Pager( + config, + pagingSourceFactory = { GeocacheUserListsDataSource(getUserLists, config) } + ).flow.cachedIn(viewModelScope) } fun importAll(geocacheList: GeocacheListEntity) { @@ -109,12 +92,14 @@ class BookmarkListViewModel( // apply additional downloading full geocache if required if (filterPreferenceManager.simpleCacheData) { list.forEach { point -> - point.setExtraOnDisplay( - context.packageName, - UpdateActivity::class.java.name, - UpdateActivity.PARAM_SIMPLE_CACHE_ID, - point.gcData.cacheID - ) + point.gcData?.cacheID?.let { cacheId -> + point.setExtraOnDisplay( + context.packageName, + UpdateActivity::class.java.name, + UpdateActivity.PARAM_SIMPLE_CACHE_ID, + cacheId + ) + } } } list @@ -146,6 +131,10 @@ class BookmarkListViewModel( action(BookmarkListAction.ChooseBookmarks(geocacheList)) } + fun handleLoadError(e: Throwable) { + action(BookmarkListAction.LoadingError(exceptionHandler(e))) + } + fun cancelProgress() { job?.cancel() } diff --git a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkViewModel.kt b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkViewModel.kt index 4903c23a..c0c0ed68 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkViewModel.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/fragment/BookmarkViewModel.kt @@ -1,15 +1,17 @@ package com.arcao.geocaching4locus.import_bookmarks.fragment +import android.annotation.SuppressLint import android.content.Context -import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Transformations -import androidx.paging.LivePagedListBuilder -import androidx.paging.PagedList +import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn import com.arcao.geocaching4locus.R import com.arcao.geocaching4locus.base.BaseViewModel import com.arcao.geocaching4locus.base.coroutine.CoroutinesDispatcherProvider -import com.arcao.geocaching4locus.base.paging.DataSourceState +import com.arcao.geocaching4locus.base.usecase.GetListGeocachesUseCase import com.arcao.geocaching4locus.base.usecase.GetPointsFromGeocacheCodesUseCase import com.arcao.geocaching4locus.base.usecase.WritePointToPackPointsFileUseCase import com.arcao.geocaching4locus.base.usecase.entity.GeocacheListEntity @@ -20,18 +22,18 @@ import com.arcao.geocaching4locus.base.util.invoke import com.arcao.geocaching4locus.error.exception.IntendedException import com.arcao.geocaching4locus.error.handler.ExceptionHandler import com.arcao.geocaching4locus.import_bookmarks.paging.ListGeocachesDataSource -import com.arcao.geocaching4locus.import_bookmarks.paging.ListGeocachesDataSourceFactory import com.arcao.geocaching4locus.settings.manager.FilterPreferenceManager import com.arcao.geocaching4locus.update.UpdateActivity import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import locus.api.manager.LocusMapManager import timber.log.Timber -@Suppress("EXPERIMENTAL_API_USAGE") +@SuppressLint("StaticFieldLeak") class BookmarkViewModel( geocacheList: GeocacheListEntity, - private val dataSourceFactory: ListGeocachesDataSourceFactory, + private val getListGeocaches: GetListGeocachesUseCase, private val context: Context, private val exceptionHandler: ExceptionHandler, private val getPointsFromGeocacheCodes: GetPointsFromGeocacheCodesUseCase, @@ -42,44 +44,32 @@ class BookmarkViewModel( dispatcherProvider: CoroutinesDispatcherProvider ) : BaseViewModel(dispatcherProvider) { - val loading = MutableLiveData() - val list: LiveData> + val pagerFlow: Flow> val selection = MutableLiveData>().apply { value = emptyList() } val action = Command() - val state: LiveData - get() = Transformations.switchMap( - dataSourceFactory.dataSource, - ListGeocachesDataSource::state - ) - private var job: Job? = null init { val pageSize = 25 - val config = PagedList.Config.Builder() - .setPageSize(pageSize) - .setInitialLoadSizeHint(pageSize * 2) - .setEnablePlaceholders(false) - .build() - - dataSourceFactory.referenceCode = geocacheList.guid - list = LivePagedListBuilder(dataSourceFactory, config).build() - - state.observeForever { state -> - if (state == DataSourceState.LoadingInitial) { - loading(true) - } else { - if (loading.value == true) { - loading(false) - } - } - if (state is DataSourceState.Error) { - action(BookmarkAction.LoadingError(exceptionHandler(state.e))) + val config = PagingConfig( + pageSize = pageSize, + enablePlaceholders = false, + initialLoadSize = 2 * pageSize + ) + + pagerFlow = Pager( + config, + pagingSourceFactory = { + ListGeocachesDataSource( + geocacheList.guid, + getListGeocaches, + config + ) } - } + ).flow.cachedIn(viewModelScope) } fun download() { @@ -121,12 +111,14 @@ class BookmarkViewModel( // apply additional downloading full geocache if required if (filterPreferenceManager.simpleCacheData) { list.forEach { point -> - point.setExtraOnDisplay( - context.packageName, - UpdateActivity::class.java.name, - UpdateActivity.PARAM_SIMPLE_CACHE_ID, - point.gcData.cacheID - ) + point.gcData?.cacheID?.let { cacheId -> + point.setExtraOnDisplay( + context.packageName, + UpdateActivity::class.java.name, + UpdateActivity.PARAM_SIMPLE_CACHE_ID, + cacheId + ) + } } } list @@ -158,4 +150,8 @@ class BookmarkViewModel( fun cancelProgress() { job?.cancel() } + + fun handleLoadError(e: Throwable) { + action(BookmarkAction.LoadingError(exceptionHandler(e))) + } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/paging/GeocacheUserListsDataSource.kt b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/paging/GeocacheUserListsDataSource.kt index bac11931..4a723bb6 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/paging/GeocacheUserListsDataSource.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/paging/GeocacheUserListsDataSource.kt @@ -1,97 +1,44 @@ package com.arcao.geocaching4locus.import_bookmarks.paging -import androidx.annotation.MainThread -import androidx.annotation.WorkerThread -import androidx.lifecycle.MutableLiveData -import androidx.paging.PageKeyedDataSource -import com.arcao.geocaching4locus.base.coroutine.CoroutinesDispatcherProvider -import com.arcao.geocaching4locus.base.paging.DataSourceState +import androidx.paging.PagingConfig +import androidx.paging.PagingSource +import androidx.paging.PagingState import com.arcao.geocaching4locus.base.usecase.GetUserListsUseCase import com.arcao.geocaching4locus.base.usecase.entity.GeocacheListEntity -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancelChildren -import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CancellationException class GeocacheUserListsDataSource( private val getUserLists: GetUserListsUseCase, - private val dispatcherProvider: CoroutinesDispatcherProvider -) : PageKeyedDataSource(), CoroutineScope { - private val job = Job() - - override val coroutineContext: CoroutineContext - get() = job + dispatcherProvider.computation - - val state = MutableLiveData().apply { - postValue(DataSourceState.LoadingInitial) - } - - init { - addInvalidatedCallback(object : InvalidatedCallback { - override fun onInvalidated() { - removeInvalidatedCallback(this) - job.cancel() - } - }) - } - - @WorkerThread - override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) { - state.postValue(DataSourceState.LoadingInitial) - - job.cancelChildren() - launch { - try { - val response = getUserLists( - skip = 0, - take = params.requestedLoadSize - ) - - val itemCount = response.totalCount - val hasNext = response.size < itemCount - callback.onResult( - response, - 0, - itemCount.toInt(), - null, - if (hasNext) response.size else null - ) - - state.postValue(DataSourceState.Done) - } catch (e: Exception) { - state.postValue(DataSourceState.Error(e)) - } + private val pagingConfig: PagingConfig +) : PagingSource() { + override suspend fun load(params: LoadParams): LoadResult { + return try { + val page = params.key ?: 0 + val skip = page * params.loadSize + val response = getUserLists( + skip = skip, + take = params.loadSize + ) + + val currentCount = skip + response.size + val totalCount = response.totalCount + + LoadResult.Page( + data = response, + prevKey = if (page == 0) null else page - 1, + nextKey = if (currentCount < totalCount) page + (params.loadSize / pagingConfig.pageSize) else null + + ) + } catch (e: Exception) { + if (e is CancellationException) throw e + LoadResult.Error(e) } } - @WorkerThread - override fun loadAfter(params: LoadParams, callback: LoadCallback) { - state.postValue(DataSourceState.LoadingNext) - - job.cancelChildren() - launch { - try { - val response = getUserLists( - skip = params.key, - take = params.requestedLoadSize - ) - - val itemCount = response.totalCount - val hasNext = (params.key + response.size) < itemCount - - callback.onResult( - response, - if (hasNext) params.key + response.size else null - ) - state.postValue(DataSourceState.Done) - } catch (e: Exception) { - state.postValue(DataSourceState.Error(e)) - } + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchorPosition -> + state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) + ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) } } - - @MainThread - override fun loadBefore(params: LoadParams, callback: LoadCallback) { - } } \ No newline at end of file diff --git a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/paging/GeocacheUserListsDataSourceFactory.kt b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/paging/GeocacheUserListsDataSourceFactory.kt deleted file mode 100644 index 2a52b37d..00000000 --- a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/paging/GeocacheUserListsDataSourceFactory.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.arcao.geocaching4locus.import_bookmarks.paging - -import androidx.annotation.WorkerThread -import androidx.lifecycle.MutableLiveData -import androidx.paging.DataSource -import com.arcao.geocaching4locus.base.coroutine.CoroutinesDispatcherProvider -import com.arcao.geocaching4locus.base.usecase.GetUserListsUseCase -import com.arcao.geocaching4locus.base.usecase.entity.GeocacheListEntity - -class GeocacheUserListsDataSourceFactory( - private val getUserLists: GetUserListsUseCase, - private val dispatcherProvider: CoroutinesDispatcherProvider -) : DataSource.Factory() { - val dataSource = MutableLiveData() - - @WorkerThread - override fun create(): DataSource { - return GeocacheUserListsDataSource(getUserLists, dispatcherProvider).also { - dataSource.postValue(it) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/paging/ListGeocachesDataSource.kt b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/paging/ListGeocachesDataSource.kt index 7866e4fc..45d95ccd 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/paging/ListGeocachesDataSource.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/paging/ListGeocachesDataSource.kt @@ -1,100 +1,46 @@ package com.arcao.geocaching4locus.import_bookmarks.paging -import androidx.annotation.MainThread -import androidx.annotation.WorkerThread -import androidx.lifecycle.MutableLiveData -import androidx.paging.PageKeyedDataSource -import com.arcao.geocaching4locus.base.coroutine.CoroutinesDispatcherProvider -import com.arcao.geocaching4locus.base.paging.DataSourceState +import androidx.paging.PagingConfig +import androidx.paging.PagingSource +import androidx.paging.PagingState import com.arcao.geocaching4locus.base.usecase.GetListGeocachesUseCase import com.arcao.geocaching4locus.base.usecase.entity.ListGeocacheEntity -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancelChildren -import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CancellationException class ListGeocachesDataSource( private val referenceCode: String, private val getListGeocaches: GetListGeocachesUseCase, - private val dispatcherProvider: CoroutinesDispatcherProvider -) : PageKeyedDataSource(), CoroutineScope { - private val job = Job() - - override val coroutineContext: CoroutineContext - get() = job + dispatcherProvider.computation - - val state = MutableLiveData().apply { - postValue(DataSourceState.LoadingInitial) - } - - init { - addInvalidatedCallback(object : InvalidatedCallback { - override fun onInvalidated() { - removeInvalidatedCallback(this) - job.cancel() - } - }) - } - - @WorkerThread - override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) { - state.postValue(DataSourceState.LoadingInitial) - - job.cancelChildren() - launch { - try { - val response = getListGeocaches( - referenceCode = referenceCode, - skip = 0, - take = params.requestedLoadSize - ) - - val itemCount = response.totalCount - val hasNext = response.size < itemCount - callback.onResult( - response, - 0, - itemCount.toInt(), - null, - if (hasNext) response.size else null - ) - - state.postValue(DataSourceState.Done) - } catch (e: Exception) { - state.postValue(DataSourceState.Error(e)) - } + private val pagingConfig: PagingConfig +) : PagingSource() { + override suspend fun load(params: LoadParams): LoadResult { + return try { + val page = params.key ?: 0 + val skip = page * params.loadSize + val response = getListGeocaches( + referenceCode = referenceCode, + skip = skip, + take = params.loadSize + ) + + val currentCount = skip + response.size + val totalCount = response.totalCount + + LoadResult.Page( + data = response, + prevKey = if (page == 0) null else page - 1, + nextKey = if (currentCount < totalCount) page + (params.loadSize / pagingConfig.pageSize) else null + + ) + } catch (e: Exception) { + if (e is CancellationException) throw e + LoadResult.Error(e) } } - @WorkerThread - override fun loadAfter(params: LoadParams, callback: LoadCallback) { - state.postValue(DataSourceState.LoadingNext) - - job.cancelChildren() - launch { - try { - val response = getListGeocaches( - referenceCode = referenceCode, - skip = params.key, - take = params.requestedLoadSize - ) - - val itemCount = response.totalCount - val hasNext = (params.key + response.size) < itemCount - - callback.onResult( - response, - if (hasNext) params.key + response.size else null - ) - state.postValue(DataSourceState.Done) - } catch (e: Exception) { - state.postValue(DataSourceState.Error(e)) - } + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchorPosition -> + state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) + ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) } } - - @MainThread - override fun loadBefore(params: LoadParams, callback: LoadCallback) { - } } \ No newline at end of file diff --git a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/paging/ListGeocachesDataSourceFactory.kt b/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/paging/ListGeocachesDataSourceFactory.kt deleted file mode 100644 index dbc8586c..00000000 --- a/app/src/main/java/com/arcao/geocaching4locus/import_bookmarks/paging/ListGeocachesDataSourceFactory.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.arcao.geocaching4locus.import_bookmarks.paging - -import androidx.annotation.WorkerThread -import androidx.lifecycle.MutableLiveData -import androidx.paging.DataSource -import com.arcao.geocaching4locus.base.coroutine.CoroutinesDispatcherProvider -import com.arcao.geocaching4locus.base.usecase.GetListGeocachesUseCase -import com.arcao.geocaching4locus.base.usecase.entity.ListGeocacheEntity - -class ListGeocachesDataSourceFactory( - private val getListGeocaches: GetListGeocachesUseCase, - private val dispatcherProvider: CoroutinesDispatcherProvider -) : DataSource.Factory() { - val dataSource = MutableLiveData() - - var referenceCode: String? = null - - @WorkerThread - override fun create(): DataSource { - return ListGeocachesDataSource(requireNotNull(referenceCode), getListGeocaches, dispatcherProvider).also { - dataSource.postValue(it) - } - } -} diff --git a/app/src/main/java/com/arcao/geocaching4locus/importgc/ImportGeocacheCodeActivity.kt b/app/src/main/java/com/arcao/geocaching4locus/importgc/ImportGeocacheCodeActivity.kt index fbf40b93..1a5ac02d 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/importgc/ImportGeocacheCodeActivity.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/importgc/ImportGeocacheCodeActivity.kt @@ -1,23 +1,30 @@ package com.arcao.geocaching4locus.importgc import android.app.Activity +import android.content.Context import android.content.Intent import android.os.Bundle -import androidx.annotation.NonNull -import com.arcao.geocaching4locus.authentication.util.requestSignOn +import androidx.activity.result.contract.ActivityResultContract +import com.arcao.geocaching4locus.authentication.LoginActivity import com.arcao.geocaching4locus.base.AbstractActionBarActivity import com.arcao.geocaching4locus.base.util.exhaustive import com.arcao.geocaching4locus.base.util.showLocusMissingError import com.arcao.geocaching4locus.base.util.withObserve -import com.arcao.geocaching4locus.data.account.AccountManager import com.arcao.geocaching4locus.error.hasPositiveAction import com.arcao.geocaching4locus.importgc.fragment.GeocacheCodesInputDialogFragment -import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel class ImportGeocacheCodeActivity : AbstractActionBarActivity(), GeocacheCodesInputDialogFragment.DialogListener { private val viewModel by viewModel() - private val accountManager by inject() + + private val loginActivity = registerForActivityResult(LoginActivity.Contract) { success -> + if (success) { + viewModel.init(intent.getStringArrayExtra(PARAM_GEOCACHES)) + } else { + setResult(Activity.RESULT_CANCELED) + finish() + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -35,16 +42,15 @@ class ImportGeocacheCodeActivity : AbstractActionBarActivity(), GeocacheCodesInp @Suppress("IMPLICIT_CAST_TO_ANY") fun handleAction(action: ImportGeocacheCodeAction) { when (action) { - ImportGeocacheCodeAction.SignIn -> { - accountManager.requestSignOn(this, REQUEST_SIGN_ON) - } + ImportGeocacheCodeAction.SignIn -> loginActivity.launch(null) is ImportGeocacheCodeAction.Error -> { startActivity(action.intent) setResult( - if (intent.hasPositiveAction()) + if (action.intent.hasPositiveAction()) { Activity.RESULT_OK - else + } else { Activity.RESULT_CANCELED + } ) finish() } @@ -68,26 +74,20 @@ class ImportGeocacheCodeActivity : AbstractActionBarActivity(), GeocacheCodesInp }.exhaustive } - override fun onInputFinished(@NonNull input: Array) { + override fun onInputFinished(input: Array) { viewModel.importGeocacheCodes(input) } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - // restart update process after log in - if (requestCode == REQUEST_SIGN_ON) { - if (resultCode == Activity.RESULT_OK) { - viewModel.init(intent.getStringArrayExtra(PARAM_GEOCACHES)) - } else { - setResult(Activity.RESULT_CANCELED) - finish() - } - } - } - companion object { - private const val REQUEST_SIGN_ON = 1 private const val PARAM_GEOCACHES = "com.arcao.geocaching4locus.GEOCACHES" } + + object Contract : ActivityResultContract() { + override fun createIntent(context: Context, input: Void?) = + Intent(context, ImportGeocacheCodeActivity::class.java) + + override fun parseResult(resultCode: Int, intent: Intent?): Boolean { + return resultCode == Activity.RESULT_OK + } + } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/importgc/ImportUrlActivity.kt b/app/src/main/java/com/arcao/geocaching4locus/importgc/ImportUrlActivity.kt index 7c6e2eb0..3e7c8815 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/importgc/ImportUrlActivity.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/importgc/ImportUrlActivity.kt @@ -1,21 +1,26 @@ package com.arcao.geocaching4locus.importgc import android.app.Activity -import android.content.Intent import android.os.Bundle -import com.arcao.geocaching4locus.authentication.util.requestSignOn +import com.arcao.geocaching4locus.authentication.LoginActivity import com.arcao.geocaching4locus.base.AbstractActionBarActivity import com.arcao.geocaching4locus.base.util.exhaustive import com.arcao.geocaching4locus.base.util.showLocusMissingError import com.arcao.geocaching4locus.base.util.withObserve -import com.arcao.geocaching4locus.data.account.AccountManager -import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import timber.log.Timber class ImportUrlActivity : AbstractActionBarActivity() { private val viewModel by viewModel() - private val accountManager by inject() + + private val loginActivity = registerForActivityResult(LoginActivity.Contract) { success -> + if (success) { + processIntent() + } else { + setResult(Activity.RESULT_CANCELED) + finish() + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -67,26 +72,7 @@ class ImportUrlActivity : AbstractActionBarActivity() { setResult(Activity.RESULT_CANCELED) finish() } - is ImportUrlAction.SignIn -> { - accountManager.requestSignOn(this, REQUEST_SIGN_ON) - } + is ImportUrlAction.SignIn -> loginActivity.launch(null) }.exhaustive } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - if (requestCode == REQUEST_SIGN_ON) { - if (resultCode == Activity.RESULT_OK) { - processIntent() - } else { - setResult(Activity.RESULT_CANCELED) - finish() - } - } - } - - companion object { - private const val REQUEST_SIGN_ON = 1 - } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/live_map/LiveMapService.kt b/app/src/main/java/com/arcao/geocaching4locus/live_map/LiveMapService.kt index 949afe68..f95d5e98 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/live_map/LiveMapService.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/live_map/LiveMapService.kt @@ -3,16 +3,16 @@ package com.arcao.geocaching4locus.live_map import android.content.ComponentName import android.content.Context import android.content.Intent +import androidx.lifecycle.LifecycleService import com.arcao.geocaching4locus.base.ProgressState import com.arcao.geocaching4locus.base.constants.AppConstants import com.arcao.geocaching4locus.base.util.ServiceUtil import com.arcao.geocaching4locus.base.util.exhaustive import com.arcao.geocaching4locus.base.util.withObserve -import com.arcao.geocaching4locus.live_map.util.LifecycleServiceFixed import com.arcao.geocaching4locus.live_map.util.LiveMapNotificationManager import org.koin.android.ext.android.inject -class LiveMapService : LifecycleServiceFixed() { +class LiveMapService : LifecycleService() { private val notificationManager by inject() private val viewModel by inject() private val onCompleteCallback: (Intent) -> Unit = { ServiceUtil.completeWakefulIntent(it) } diff --git a/app/src/main/java/com/arcao/geocaching4locus/live_map/LiveMapViewModel.kt b/app/src/main/java/com/arcao/geocaching4locus/live_map/LiveMapViewModel.kt index 49bcbb5b..d2c1f201 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/live_map/LiveMapViewModel.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/live_map/LiveMapViewModel.kt @@ -1,10 +1,10 @@ package com.arcao.geocaching4locus.live_map +import android.annotation.SuppressLint import android.content.Context import android.content.Intent -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleObserver -import androidx.lifecycle.OnLifecycleEvent +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.viewModelScope import com.arcao.geocaching4locus.R import com.arcao.geocaching4locus.base.AccountNotFoundException @@ -37,6 +37,7 @@ import java.io.IOException import java.util.concurrent.CancellationException import java.util.concurrent.Executors +@SuppressLint("StaticFieldLeak") class LiveMapViewModel( private val context: Context, private val notificationManager: LiveMapNotificationManager, @@ -47,7 +48,7 @@ class LiveMapViewModel( private val removeLocusMapPoints: RemoveLocusMapPointsUseCase, private val accountManager: AccountManager, dispatcherProvider: CoroutinesDispatcherProvider -) : BaseViewModel(dispatcherProvider), LifecycleObserver { +) : BaseViewModel(dispatcherProvider), DefaultLifecycleObserver { private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() fun addTask(intent: Intent, completionCallback: (Intent) -> Unit) { @@ -100,8 +101,7 @@ class LiveMapViewModel( filterPreferenceManager.difficultyMin, filterPreferenceManager.difficultyMax, filterPreferenceManager.terrainMin, - filterPreferenceManager.terrainMax, - filterPreferenceManager.excludeIgnoreList + filterPreferenceManager.terrainMax ) { count = it }.map { list -> receivedGeocaches += list.size requests++ @@ -113,7 +113,7 @@ class LiveMapViewModel( context.packageName, UpdateActivity::class.java.name, UpdateActivity.PARAM_SIMPLE_CACHE_ID, - point.gcData.cacheID + requireNotNull(point.gcData).cacheID ) } list @@ -178,8 +178,7 @@ class LiveMapViewModel( } } - @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) - override fun onCleared() { + override fun onDestroy(owner: LifecycleOwner) { viewModelScope.cancel() } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/live_map/receiver/LiveMapBroadcastReceiver.kt b/app/src/main/java/com/arcao/geocaching4locus/live_map/receiver/LiveMapBroadcastReceiver.kt index b2dd3ef5..1671577f 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/live_map/receiver/LiveMapBroadcastReceiver.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/live_map/receiver/LiveMapBroadcastReceiver.kt @@ -10,8 +10,8 @@ import locus.api.android.ActionBasics import locus.api.android.utils.LocusUtils import locus.api.extension.isInvalid import locus.api.objects.extra.Location -import org.koin.core.KoinComponent -import org.koin.core.inject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import timber.log.Timber import kotlin.math.max import kotlin.math.min @@ -71,17 +71,17 @@ class LiveMapBroadcastReceiver : BroadcastReceiver(), KoinComponent { return // bug in Locus Map? - val leftLongitude = min(mapTopLeft.getLongitude(), mapBottomRight.getLongitude()) - val rightLongitude = max(mapTopLeft.getLongitude(), mapBottomRight.getLongitude()) + val leftLongitude = min(mapTopLeft.longitude, mapBottomRight.longitude) + val rightLongitude = max(mapTopLeft.longitude, mapBottomRight.longitude) // Start service to retrieve caches LiveMapService.start( context, - mapCenter.getLatitude(), - mapCenter.getLongitude(), - mapTopLeft.getLatitude(), + mapCenter.latitude, + mapCenter.longitude, + mapTopLeft.latitude, leftLongitude, - mapBottomRight.getLatitude(), + mapBottomRight.latitude, rightLongitude ) } diff --git a/app/src/main/java/com/arcao/geocaching4locus/live_map/util/LifecycleServiceFixed.kt b/app/src/main/java/com/arcao/geocaching4locus/live_map/util/LifecycleServiceFixed.kt deleted file mode 100644 index d7707829..00000000 --- a/app/src/main/java/com/arcao/geocaching4locus/live_map/util/LifecycleServiceFixed.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.arcao.geocaching4locus.live_map.util - -import android.app.Service -import android.content.Intent -import android.os.IBinder - -import androidx.annotation.CallSuper -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ServiceLifecycleDispatcher - -/** - * A Service that is also a [LifecycleOwner]. - */ -abstract class LifecycleServiceFixed : Service(), LifecycleOwner { - - private val mDispatcher = ServiceLifecycleDispatcher(this) - - @CallSuper - override fun onCreate() { - mDispatcher.onServicePreSuperOnCreate() - super.onCreate() - } - - @CallSuper - override fun onBind(intent: Intent): IBinder? { - mDispatcher.onServicePreSuperOnBind() - return null - } - - @Suppress("DEPRECATION") - @CallSuper - override fun onStart(intent: Intent?, startId: Int) { - mDispatcher.onServicePreSuperOnStart() - super.onStart(intent, startId) - } - - // this method is added only to annotate it with @CallSuper. - // In usual service super.onStartCommand is no-op, but in LifecycleService - // it results in mDispatcher.onServicePreSuperOnStart() call, because - // super.onStartCommand calls onStart(). - @CallSuper - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - return super.onStartCommand(intent, flags, startId) - } - - @CallSuper - override fun onDestroy() { - mDispatcher.onServicePreSuperOnDestroy() - super.onDestroy() - } - - override fun getLifecycle(): Lifecycle { - return mDispatcher.lifecycle - } -} diff --git a/app/src/main/java/com/arcao/geocaching4locus/live_map/util/LiveMapNotificationManager.kt b/app/src/main/java/com/arcao/geocaching4locus/live_map/util/LiveMapNotificationManager.kt index 2029fef4..438908c3 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/live_map/util/LiveMapNotificationManager.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/live_map/util/LiveMapNotificationManager.kt @@ -31,6 +31,7 @@ import com.arcao.geocaching4locus.live_map.receiver.LiveMapBroadcastReceiver import com.arcao.geocaching4locus.settings.SettingsActivity import com.arcao.geocaching4locus.settings.fragment.LiveMapPreferenceFragment import com.arcao.geocaching4locus.settings.manager.DefaultPreferenceManager +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import locus.api.manager.LocusMapManager @@ -44,8 +45,10 @@ class LiveMapNotificationManager( private val locusMapManager: LocusMapManager, private val dispatcherProvider: CoroutinesDispatcherProvider ) : SharedPreferences.OnSharedPreferenceChangeListener { - private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) - private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + private val preferences: SharedPreferences = + PreferenceManager.getDefaultSharedPreferences(context) + private val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager private val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager private val stateChangeListeners = CopyOnWriteArraySet() @@ -79,9 +82,6 @@ class LiveMapNotificationManager( } } - // make sure Live Map broadcast receiver is always enabled - locusMapManager.enablePeriodicUpdatesReceiver(LiveMapBroadcastReceiver::class) - preferences.edit { putBoolean(PrefConstants.LIVE_MAP, willBeEnabled) } @@ -213,7 +213,10 @@ class LiveMapNotificationManager( private fun showNotification() { notificationShown = true - notificationManager.notify(AppConstants.NOTIFICATION_ID_LIVEMAP, createNotification().build()) + notificationManager.notify( + AppConstants.NOTIFICATION_ID_LIVEMAP, + createNotification().build() + ) } private fun hideNotification() { @@ -253,7 +256,12 @@ class LiveMapNotificationManager( } val pendingIntent = - createPendingActivityIntent(SettingsActivity.createIntent(context, LiveMapPreferenceFragment::class.java)) + createPendingActivityIntent( + SettingsActivity.Contract.createIntent( + context, + LiveMapPreferenceFragment::class.java + ) + ) nb.addAction( R.drawable.ic_stat_live_map_settings, context.getText(R.string.notify_live_map_action_settings), @@ -271,18 +279,26 @@ class LiveMapNotificationManager( return nb } - private fun createPendingActivityIntent(intent: Intent): PendingIntent { - return PendingIntent.getActivity( - context, 0, - intent, + private fun createPendingActivityIntent(intent: Intent) = PendingIntent.getActivity( + context, 0, + intent, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + } else { PendingIntent.FLAG_UPDATE_CURRENT - ) - } - - private fun createPendingBroadcastIntent(action: String): PendingIntent { - val intent = Intent(action, null, context, LiveMapBroadcastReceiver::class.java) - return PendingIntent.getBroadcast(context, 0, intent, 0) - } + } + ) + + private fun createPendingBroadcastIntent(action: String) = PendingIntent.getBroadcast( + context, + 0, + Intent(action, null, context, LiveMapBroadcastReceiver::class.java), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.FLAG_IMMUTABLE + } else { + 0 + } + ) private fun showError(@StringRes message: Int) { context.startActivity( @@ -331,6 +347,7 @@ class LiveMapNotificationManager( } } + @OptIn(DelicateCoroutinesApi::class) fun removeLiveMapItems() = GlobalScope.launch(dispatcherProvider.computation) { val lastRequests = defaultPreferenceManager.liveMapLastRequests if (lastRequests > 0) { @@ -346,9 +363,12 @@ class LiveMapNotificationManager( companion object { private const val VAR_B_MAP_VISIBLE = "1300" - private const val ACTION_HIDE_NOTIFICATION = "com.arcao.geocaching4locus.action.HIDE_NOTIFICATION" - private const val ACTION_LIVE_MAP_ENABLE = "com.arcao.geocaching4locus.action.LIVE_MAP_ENABLE" - private const val ACTION_LIVE_MAP_DISABLE = "com.arcao.geocaching4locus.action.LIVE_MAP_DISABLE" + private const val ACTION_HIDE_NOTIFICATION = + "com.arcao.geocaching4locus.action.HIDE_NOTIFICATION" + private const val ACTION_LIVE_MAP_ENABLE = + "com.arcao.geocaching4locus.action.LIVE_MAP_ENABLE" + private const val ACTION_LIVE_MAP_DISABLE = + "com.arcao.geocaching4locus.action.LIVE_MAP_DISABLE" private const val NOTIFICATION_TIMEOUT_MS: Long = 2200 private const val NOTIFICATION_CHANNEL_ID = "LIVE_MAP_NOTIFICATION_CHANNEL" diff --git a/app/src/main/java/com/arcao/geocaching4locus/search_nearest/SearchNearestActivity.kt b/app/src/main/java/com/arcao/geocaching4locus/search_nearest/SearchNearestActivity.kt index af3060d2..61927b9a 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/search_nearest/SearchNearestActivity.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/search_nearest/SearchNearestActivity.kt @@ -1,14 +1,17 @@ package com.arcao.geocaching4locus.search_nearest import android.app.Activity +import android.content.Context import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem +import androidx.activity.result.contract.ActivityResultContract +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.Toolbar import androidx.databinding.DataBindingUtil import com.arcao.geocaching4locus.R -import com.arcao.geocaching4locus.authentication.util.requestSignOn +import com.arcao.geocaching4locus.authentication.LoginActivity import com.arcao.geocaching4locus.base.AbstractActionBarActivity import com.arcao.geocaching4locus.base.fragment.SliderDialogFragment import com.arcao.geocaching4locus.base.util.PermissionUtil @@ -16,14 +19,12 @@ import com.arcao.geocaching4locus.base.util.exhaustive import com.arcao.geocaching4locus.base.util.invoke import com.arcao.geocaching4locus.base.util.showLocusMissingError import com.arcao.geocaching4locus.base.util.withObserve -import com.arcao.geocaching4locus.data.account.AccountManager import com.arcao.geocaching4locus.databinding.ActivitySearchNearestBinding import com.arcao.geocaching4locus.error.ErrorActivity import com.arcao.geocaching4locus.search_nearest.fragment.NoLocationPermissionErrorDialogFragment import com.arcao.geocaching4locus.search_nearest.fragment.NoLocationProviderDialogFragment import com.arcao.geocaching4locus.settings.SettingsActivity import com.arcao.geocaching4locus.settings.fragment.FilterPreferenceFragment -import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -32,10 +33,23 @@ class SearchNearestActivity : AbstractActionBarActivity(), SliderDialogFragment. parametersOf(intent) } - private val accountManager by inject() - private lateinit var binding: ActivitySearchNearestBinding + private val settingsActivity = registerForActivityResult(SettingsActivity.Contract) {} + private val requestLocationPermission = registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions() + ) { result -> + if (result.all { it.value }) { + viewModel.retrieveCoordinates() + } else { + NoLocationPermissionErrorDialogFragment.newInstance().show(supportFragmentManager) + } + } + + private val loginActivity = registerForActivityResult(LoginActivity.Contract) { success -> + if (success) viewModel.download() + } + public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -43,6 +57,7 @@ class SearchNearestActivity : AbstractActionBarActivity(), SliderDialogFragment. binding.lifecycleOwner = this binding.vm = viewModel + @Suppress("USELESS_CAST") val toolbar = binding.toolbar as Toolbar val latitude = binding.incCoordinates.latitude val longitude = binding.incCoordinates.longitude @@ -68,9 +83,7 @@ class SearchNearestActivity : AbstractActionBarActivity(), SliderDialogFragment. @Suppress("IMPLICIT_CAST_TO_ANY") fun handleAction(action: SearchNearestAction) { when (action) { - SearchNearestAction.SignIn -> { - accountManager.requestSignOn(this, REQUEST_SIGN_ON) - } + SearchNearestAction.SignIn -> loginActivity.launch(null) is SearchNearestAction.Error -> { startActivity(action.intent) setResult(Activity.RESULT_CANCELED) @@ -80,33 +93,30 @@ class SearchNearestActivity : AbstractActionBarActivity(), SliderDialogFragment. setResult(Activity.RESULT_OK) finish() } - is SearchNearestAction.LocusMapNotInstalled -> { - showLocusMissingError() - } - SearchNearestAction.RequestGpsLocationPermission -> { - PermissionUtil.requestGpsLocationPermission(this, REQUEST_LOCATION_PERMISSION) - } - SearchNearestAction.RequestWifiLocationPermission -> { - PermissionUtil.requestWifiLocationPermission(this, REQUEST_LOCATION_PERMISSION) - } - SearchNearestAction.WrongCoordinatesFormat -> { - startActivity(ErrorActivity.IntentBuilder(this).message(R.string.error_coordinates_format).build()) - } + is SearchNearestAction.LocusMapNotInstalled -> showLocusMissingError() + SearchNearestAction.RequestGpsLocationPermission -> requestLocationPermission.launch( + PermissionUtil.PERMISSION_LOCATION_GPS + ) + SearchNearestAction.RequestWifiLocationPermission -> requestLocationPermission.launch( + PermissionUtil.PERMISSION_LOCATION_WIFI + ) + SearchNearestAction.WrongCoordinatesFormat -> startActivity( + ErrorActivity.IntentBuilder(this) + .message(R.string.error_coordinates_format) + .build() + ) SearchNearestAction.ShowFilters -> { - startActivity(SettingsActivity.createIntent(this, FilterPreferenceFragment::class.java)) + settingsActivity.launch(FilterPreferenceFragment::class.java) } - SearchNearestAction.LocationProviderDisabled -> { + SearchNearestAction.LocationProviderDisabled -> NoLocationProviderDialogFragment.newInstance().show(supportFragmentManager) - } - is SearchNearestAction.RequestCacheCount -> { - SliderDialogFragment.newInstance( - title = R.string.title_geocache_count, - min = action.step, - max = action.max, - step = action.step, - defaultValue = action.value - ).show(supportFragmentManager, "COUNTER") - } + is SearchNearestAction.RequestCacheCount -> SliderDialogFragment.newInstance( + title = R.string.title_geocache_count, + min = action.step, + max = action.max, + step = action.step, + defaultValue = action.value + ).show(supportFragmentManager, "COUNTER") }.exhaustive } @@ -117,7 +127,7 @@ class SearchNearestActivity : AbstractActionBarActivity(), SliderDialogFragment. override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { R.id.main_activity_option_menu_preferences -> { - startActivity(SettingsActivity.createIntent(this)) + settingsActivity.launch(null) true } android.R.id.home -> { @@ -127,27 +137,6 @@ class SearchNearestActivity : AbstractActionBarActivity(), SliderDialogFragment. else -> super.onOptionsItemSelected(item) } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - // restart download process after log in - if (requestCode == REQUEST_SIGN_ON && resultCode == Activity.RESULT_OK) { - viewModel.download() - } - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - - if (requestCode == REQUEST_LOCATION_PERMISSION) { - if (PermissionUtil.verifyPermissions(grantResults)) { - viewModel.retrieveCoordinates() - } else { - NoLocationPermissionErrorDialogFragment.newInstance().show(supportFragmentManager) - } - } - } - override fun onProgressCancel(requestId: Int) { viewModel.cancelProgress() } @@ -157,8 +146,14 @@ class SearchNearestActivity : AbstractActionBarActivity(), SliderDialogFragment. viewModel.requestedCaches(fragment.getValue()) } - companion object { - private const val REQUEST_SIGN_ON = 1 - private const val REQUEST_LOCATION_PERMISSION = 2 + object Contract : ActivityResultContract() { + override fun createIntent(context: Context, input: Intent?) = + Intent(context, SearchNearestActivity::class.java).apply { + input?.let { putExtras(it) } + } + + override fun parseResult(resultCode: Int, intent: Intent?): Boolean { + return resultCode == Activity.RESULT_OK + } } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/search_nearest/SearchNearestViewModel.kt b/app/src/main/java/com/arcao/geocaching4locus/search_nearest/SearchNearestViewModel.kt index d183560d..ea3a6b1a 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/search_nearest/SearchNearestViewModel.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/search_nearest/SearchNearestViewModel.kt @@ -1,6 +1,6 @@ package com.arcao.geocaching4locus.search_nearest -import android.content.Context +import android.app.Application import android.content.Intent import androidx.lifecycle.MutableLiveData import com.arcao.geocaching4locus.R @@ -32,7 +32,7 @@ import timber.log.Timber class SearchNearestViewModel( intent: Intent, - private val context: Context, + private val context: Application, private val accountManager: AccountManager, private val preferenceManager: DefaultPreferenceManager, private val filterPreferenceManager: FilterPreferenceManager, @@ -212,7 +212,6 @@ class SearchNearestViewModel( filterPreferenceManager.difficultyMax, filterPreferenceManager.terrainMin, filterPreferenceManager.terrainMax, - filterPreferenceManager.excludeIgnoreList, maxCount ) { count = it }.map { list -> receivedGeocaches += list.size @@ -221,12 +220,14 @@ class SearchNearestViewModel( // apply additional downloading full geocache if required if (filterPreferenceManager.simpleCacheData) { list.forEach { point -> - point.setExtraOnDisplay( - context.packageName, - UpdateActivity::class.java.name, - UpdateActivity.PARAM_SIMPLE_CACHE_ID, - point.gcData.cacheID - ) + point.gcData?.cacheID?.let { cacheId -> + point.setExtraOnDisplay( + context.packageName, + UpdateActivity::class.java.name, + UpdateActivity.PARAM_SIMPLE_CACHE_ID, + cacheId + ) + } } } list diff --git a/app/src/main/java/com/arcao/geocaching4locus/settings/SettingsActivity.kt b/app/src/main/java/com/arcao/geocaching4locus/settings/SettingsActivity.kt index 739ec7f8..7aaed440 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/settings/SettingsActivity.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/settings/SettingsActivity.kt @@ -1,9 +1,11 @@ package com.arcao.geocaching4locus.settings +import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle import android.view.MenuItem +import androidx.activity.result.contract.ActivityResultContract import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment import androidx.fragment.app.commit @@ -65,7 +67,10 @@ class SettingsActivity : AbstractActionBarActivity(), supportFragmentManager.commit { replace( R.id.fragment, - supportFragmentManager.fragmentFactory.instantiate(classLoader, pref.fragment) + supportFragmentManager.fragmentFactory.instantiate( + classLoader, + pref.fragment ?: return@commit + ) ) addToBackStack(null) } @@ -75,7 +80,7 @@ class SettingsActivity : AbstractActionBarActivity(), override fun onPreferenceDisplayDialog( caller: PreferenceFragmentCompat, - pref: Preference? + pref: Preference ): Boolean { if (pref is PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback) { return pref.onPreferenceDisplayDialog(caller, pref) @@ -86,16 +91,18 @@ class SettingsActivity : AbstractActionBarActivity(), companion object { private const val EXTRA_SHOW_FRAGMENT = ":android:show_fragment" + } - fun createIntent(context: Context): Intent { - return Intent(context, SettingsActivity::class.java) - } + object Contract : ActivityResultContract?, Boolean>() { + override fun createIntent(context: Context, input: Class?) = + Intent(context, SettingsActivity::class.java).apply { + if (input != null) { + putExtra(EXTRA_SHOW_FRAGMENT, input.name) + } + } - fun createIntent( - context: Context, - preferenceFragment: Class - ): Intent { - return createIntent(context).putExtra(EXTRA_SHOW_FRAGMENT, preferenceFragment.name) + override fun parseResult(resultCode: Int, intent: Intent?): Boolean { + return resultCode == Activity.RESULT_OK } } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/AboutPreferenceFragment.kt b/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/AboutPreferenceFragment.kt index d50ef5c6..28158997 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/AboutPreferenceFragment.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/AboutPreferenceFragment.kt @@ -66,7 +66,7 @@ class AboutPreferenceFragment : AbstractPreferenceFragment(), CoroutineScope { R.string.feedback_body ) - startActivityForResult(intent, REQ_FEEDBACK) + startActivity(intent) } true } @@ -99,8 +99,4 @@ class AboutPreferenceFragment : AbstractPreferenceFragment(), CoroutineScope { const val TAG = "DonatePaypalDialogFragment" } } - - companion object { - const val REQ_FEEDBACK = 1 - } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/AccountsPreferenceFragment.kt b/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/AccountsPreferenceFragment.kt index 72c5d77e..a5c1fe11 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/AccountsPreferenceFragment.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/AccountsPreferenceFragment.kt @@ -2,7 +2,7 @@ package com.arcao.geocaching4locus.settings.fragment import androidx.preference.Preference import com.arcao.geocaching4locus.R -import com.arcao.geocaching4locus.authentication.util.requestSignOn +import com.arcao.geocaching4locus.authentication.LoginActivity import com.arcao.geocaching4locus.base.constants.AppConstants import com.arcao.geocaching4locus.base.constants.PrefConstants.ACCOUNT_POWERED_BY import com.arcao.geocaching4locus.base.fragment.AbstractPreferenceFragment @@ -14,6 +14,8 @@ import org.koin.android.ext.android.inject class AccountsPreferenceFragment : AbstractPreferenceFragment() { val accountManager by inject() + private val loginActivity = registerForActivityResult(LoginActivity.Contract) {} + override val preferenceResource: Int get() = R.xml.preference_category_accounts @@ -27,7 +29,7 @@ class AccountsPreferenceFragment : AbstractPreferenceFragment() { setTitle(R.string.pref_login) setSummary(R.string.pref_login_summary) } else { - accountManager.requestSignOn(requireActivity(), 0) + loginActivity.launch(null) } true diff --git a/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/CacheTypeFilterPreferenceFragment.kt b/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/CacheTypeFilterPreferenceFragment.kt index a20911d6..9bbe2d94 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/CacheTypeFilterPreferenceFragment.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/CacheTypeFilterPreferenceFragment.kt @@ -1,52 +1,60 @@ package com.arcao.geocaching4locus.settings.fragment.filter +import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import android.view.View +import androidx.core.view.MenuProvider import androidx.preference.CheckBoxPreference import com.arcao.geocaching4locus.R import com.arcao.geocaching4locus.base.constants.AppConstants -import com.arcao.geocaching4locus.base.constants.PrefConstants.FILTER_CACHE_TYPE_PREFIX +import com.arcao.geocaching4locus.base.constants.PrefConstants import com.arcao.geocaching4locus.base.fragment.AbstractPreferenceFragment class CacheTypeFilterPreferenceFragment : AbstractPreferenceFragment() { override val preferenceResource: Int get() = R.xml.preference_category_filter_cache_type - override fun preparePreference() { - super.preparePreference() - - setHasOptionsMenu(true) - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - super.onCreateOptionsMenu(menu, inflater) - inflater.inflate(R.menu.toolbar_select_deselect, menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - val geocacheTypeLength = AppConstants.GEOCACHE_TYPES.size - - when (item.itemId) { - android.R.id.home -> { - // app icon in action bar clicked; go home - requireActivity().finish() - return true - } + private val menuProvider = object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.toolbar_select_deselect, menu) + } - R.id.selectAll -> { - for (i in 0 until geocacheTypeLength) - preference(FILTER_CACHE_TYPE_PREFIX + i).isChecked = true - return true + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + val geocacheTypeLength = AppConstants.GEOCACHE_TYPES.size + + return when (menuItem.itemId) { + android.R.id.home -> { + // app icon in action bar clicked; go home + requireActivity().finish() + true + } + + R.id.selectAll -> { + for (i in 0 until geocacheTypeLength) { + preference(PrefConstants.FILTER_CACHE_TYPE_PREFIX + i) + .isChecked = true + } + true + } + + R.id.deselectAll -> { + for (i in 0 until geocacheTypeLength) { + preference(PrefConstants.FILTER_CACHE_TYPE_PREFIX + i) + .isChecked = false + } + true + } + + else -> false } + } + } - R.id.deselectAll -> { - for (i in 0 until geocacheTypeLength) - preference(FILTER_CACHE_TYPE_PREFIX + i).isChecked = false - return true - } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) - else -> return super.onOptionsItemSelected(item) - } + requireActivity().addMenuProvider(menuProvider, viewLifecycleOwner) } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/ContainerTypeFilterPreferenceFragment.kt b/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/ContainerTypeFilterPreferenceFragment.kt index e39f8ae3..1d0f29ee 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/ContainerTypeFilterPreferenceFragment.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/ContainerTypeFilterPreferenceFragment.kt @@ -1,8 +1,11 @@ package com.arcao.geocaching4locus.settings.fragment.filter +import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import android.view.View +import androidx.core.view.MenuProvider import androidx.preference.CheckBoxPreference import com.arcao.geocaching4locus.R import com.arcao.geocaching4locus.base.constants.AppConstants @@ -13,40 +16,43 @@ class ContainerTypeFilterPreferenceFragment : AbstractPreferenceFragment() { override val preferenceResource: Int get() = R.xml.preference_category_filter_container_type - override fun preparePreference() { - super.preparePreference() - - setHasOptionsMenu(true) - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - super.onCreateOptionsMenu(menu, inflater) - inflater.inflate(R.menu.toolbar_select_deselect, menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - val containerTypeLength = AppConstants.GEOCACHE_SIZES.size - - when (item.itemId) { - android.R.id.home -> { - // app icon in action bar clicked; go home - requireActivity().finish() - return true - } + private val menuProvider = object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.toolbar_select_deselect, menu) + } - R.id.selectAll -> { - for (i in 0 until containerTypeLength) - preference(FILTER_CONTAINER_TYPE_PREFIX + i).isChecked = true - return true + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + val containerTypeLength = AppConstants.GEOCACHE_SIZES.size + + return when (menuItem.itemId) { + android.R.id.home -> { + // app icon in action bar clicked; go home + requireActivity().finish() + true + } + + R.id.selectAll -> { + for (i in 0 until containerTypeLength) + preference(FILTER_CONTAINER_TYPE_PREFIX + i).isChecked = + true + true + } + + R.id.deselectAll -> { + for (i in 0 until containerTypeLength) + preference(FILTER_CONTAINER_TYPE_PREFIX + i).isChecked = + false + true + } + + else -> false } + } + } - R.id.deselectAll -> { - for (i in 0 until containerTypeLength) - preference(FILTER_CONTAINER_TYPE_PREFIX + i).isChecked = false - return true - } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) - else -> return super.onOptionsItemSelected(item) - } + requireActivity().addMenuProvider(menuProvider, viewLifecycleOwner) } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/DifficultyFilterPreferenceFragment.kt b/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/DifficultyFilterPreferenceFragment.kt index f7c5942c..0e67a511 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/DifficultyFilterPreferenceFragment.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/DifficultyFilterPreferenceFragment.kt @@ -49,7 +49,7 @@ class DifficultyFilterPreferenceFragment : AbstractPreferenceFragment() { FILTER_DIFFICULTY_MIN, FILTER_DIFFICULTY_MAX -> { preference(key).apply { - summary = prepareRatingSummary(entry) + summary = prepareRatingSummary(entry ?: return) } } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/TerrainFilterPreferenceFragment.kt b/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/TerrainFilterPreferenceFragment.kt index 8f38db12..df513698 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/TerrainFilterPreferenceFragment.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/settings/fragment/filter/TerrainFilterPreferenceFragment.kt @@ -49,7 +49,7 @@ class TerrainFilterPreferenceFragment : AbstractPreferenceFragment() { FILTER_TERRAIN_MIN, FILTER_TERRAIN_MAX -> { preference(key).apply { - summary = prepareRatingSummary(entry) + summary = prepareRatingSummary(entry ?: return) } } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/settings/widget/SliderPreference.kt b/app/src/main/java/com/arcao/geocaching4locus/settings/widget/SliderPreference.kt index 8fddc99c..4c9a5773 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/settings/widget/SliderPreference.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/settings/widget/SliderPreference.kt @@ -5,7 +5,6 @@ import android.content.res.TypedArray import android.os.Parcel import android.os.Parcelable import android.util.AttributeSet -import androidx.annotation.Nullable import androidx.core.content.withStyledAttributes import androidx.preference.DialogPreference import androidx.preference.Preference @@ -50,7 +49,7 @@ class SliderPreference @JvmOverloads constructor( return R.layout.view_slider } - override fun onSetInitialValue(@Nullable defaultValue: Any?) { + override fun onSetInitialValue(defaultValue: Any?) { progress = getPersistedInt(defaultValue as? Int ?: 0) } @@ -58,7 +57,7 @@ class SliderPreference @JvmOverloads constructor( return a.getInt(index, 0) } - override fun onSaveInstanceState(): Parcelable { + override fun onSaveInstanceState(): Parcelable? { val superState = super.onSaveInstanceState() if (isPersistent) { @@ -85,7 +84,10 @@ class SliderPreference @JvmOverloads constructor( value = myState.value } - override fun onPreferenceDisplayDialog(caller: PreferenceFragmentCompat, preference: Preference): Boolean { + override fun onPreferenceDisplayDialog( + caller: PreferenceFragmentCompat, + preference: Preference + ): Boolean { // check if dialog is already showing val fragmentManager = caller.parentFragmentManager if (fragmentManager.findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) return true @@ -99,9 +101,9 @@ class SliderPreference @JvmOverloads constructor( } private class SavedState : BaseSavedState { - internal var value: Int = 0 + var value: Int = 0 - internal constructor(source: Parcel) : super(source) { + constructor(source: Parcel) : super(source) { value = source.readInt() } @@ -110,7 +112,7 @@ class SliderPreference @JvmOverloads constructor( dest.writeInt(value) } - internal constructor(superState: Parcelable) : super(superState) + constructor(superState: Parcelable?) : super(superState) companion object { @Suppress("unused") diff --git a/app/src/main/java/com/arcao/geocaching4locus/settings/widget/SliderPreferenceDialogFragment.kt b/app/src/main/java/com/arcao/geocaching4locus/settings/widget/SliderPreferenceDialogFragment.kt index e1f5184b..e234e075 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/settings/widget/SliderPreferenceDialogFragment.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/settings/widget/SliderPreferenceDialogFragment.kt @@ -10,7 +10,6 @@ import android.view.View import android.widget.EditText import android.widget.SeekBar import android.widget.TextView -import androidx.annotation.NonNull import androidx.core.os.bundleOf import androidx.preference.PreferenceDialogFragmentCompat import com.arcao.geocaching4locus.R @@ -40,12 +39,12 @@ class SliderPreferenceDialogFragment : PreferenceDialogFragmentCompat() { } } - override fun onSaveInstanceState(@NonNull outState: Bundle) { + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putInt(SAVE_STATE_VALUE, value) } - override fun onBindDialogView(@NonNull v: View) { + override fun onBindDialogView(v: View) { val messageView = v.findViewById(R.id.message) val seekBar = v.findViewById(R.id.seekbar) val editText = v.findViewById(R.id.input) @@ -110,8 +109,8 @@ class SliderPreferenceDialogFragment : PreferenceDialogFragmentCompat() { } } - private class InputTextFilter internal constructor( - internal val editText: EditText, + private class InputTextFilter( + val editText: EditText, private val min: Int, private val max: Int, step: Int @@ -136,19 +135,18 @@ class SliderPreferenceDialogFragment : PreferenceDialogFragmentCompat() { return InputType.TYPE_CLASS_TEXT } - @NonNull override fun getAcceptedChars(): CharArray { return DIGIT_CHARACTERS } override fun filter( - @NonNull source: CharSequence, + source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int - ): CharSequence? { + ): CharSequence { if (availableValues == null || availableValues.isEmpty()) { var filtered: CharSequence? = super.filter(source, start, end, dest, dstart, dend) if (filtered == null) { diff --git a/app/src/main/java/com/arcao/geocaching4locus/update/UpdateActivity.kt b/app/src/main/java/com/arcao/geocaching4locus/update/UpdateActivity.kt index 3dfbdab7..c6dca802 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/update/UpdateActivity.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/update/UpdateActivity.kt @@ -1,23 +1,28 @@ package com.arcao.geocaching4locus.update import android.app.Activity -import android.content.Intent import android.os.Bundle import com.arcao.geocaching4locus.R -import com.arcao.geocaching4locus.authentication.util.requestSignOn +import com.arcao.geocaching4locus.authentication.LoginActivity import com.arcao.geocaching4locus.base.AbstractActionBarActivity import com.arcao.geocaching4locus.base.util.exhaustive import com.arcao.geocaching4locus.base.util.showLocusMissingError import com.arcao.geocaching4locus.base.util.withObserve -import com.arcao.geocaching4locus.data.account.AccountManager import com.arcao.geocaching4locus.error.ErrorActivity -import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import timber.log.Timber class UpdateActivity : AbstractActionBarActivity() { private val viewModel by viewModel() - private val accountManager by inject() + + private val loginActivity = registerForActivityResult(LoginActivity.Contract) { success -> + if (success) { + viewModel.processIntent(intent) + } else { + setResult(Activity.RESULT_CANCELED) + finish() + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -33,9 +38,7 @@ class UpdateActivity : AbstractActionBarActivity() { @Suppress("IMPLICIT_CAST_TO_ANY") fun handleAction(action: UpdateAction) { when (action) { - UpdateAction.SignIn -> { - accountManager.requestSignOn(this, REQUEST_SIGN_ON) - } + UpdateAction.SignIn -> loginActivity.launch(null) is UpdateAction.Error -> { Timber.d("UpdateAction.Error intent: %s", intent) startActivity(action.intent) @@ -64,20 +67,6 @@ class UpdateActivity : AbstractActionBarActivity() { }.exhaustive } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - // restart update process after log in - if (requestCode == REQUEST_SIGN_ON) { - if (resultCode == Activity.RESULT_OK) { - viewModel.processIntent(intent) - } else { - setResult(Activity.RESULT_CANCELED) - finish() - } - } - } - override fun onProgressCancel(requestId: Int) { viewModel.cancelProgress() } @@ -85,7 +74,5 @@ class UpdateActivity : AbstractActionBarActivity() { companion object { const val PARAM_CACHE_ID = "cacheId" const val PARAM_SIMPLE_CACHE_ID = "simpleCacheId" - - private const val REQUEST_SIGN_ON = 1 } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/update/UpdateMoreActivity.kt b/app/src/main/java/com/arcao/geocaching4locus/update/UpdateMoreActivity.kt index 92994850..0528f65b 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/update/UpdateMoreActivity.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/update/UpdateMoreActivity.kt @@ -1,21 +1,26 @@ package com.arcao.geocaching4locus.update import android.app.Activity -import android.content.Intent import android.os.Bundle -import com.arcao.geocaching4locus.authentication.util.requestSignOn +import com.arcao.geocaching4locus.authentication.LoginActivity import com.arcao.geocaching4locus.base.AbstractActionBarActivity import com.arcao.geocaching4locus.base.util.exhaustive import com.arcao.geocaching4locus.base.util.showLocusMissingError import com.arcao.geocaching4locus.base.util.withObserve -import com.arcao.geocaching4locus.data.account.AccountManager -import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import timber.log.Timber class UpdateMoreActivity : AbstractActionBarActivity() { private val viewModel by viewModel() - private val accountManager by inject() + + private val loginActivity = registerForActivityResult(LoginActivity.Contract) { success -> + if (success) { + viewModel.processIntent(intent) + } else { + setResult(Activity.RESULT_CANCELED) + finish() + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -31,9 +36,7 @@ class UpdateMoreActivity : AbstractActionBarActivity() { @Suppress("IMPLICIT_CAST_TO_ANY") fun handleAction(action: UpdateMoreAction) { when (action) { - UpdateMoreAction.SignIn -> { - accountManager.requestSignOn(this, REQUEST_SIGN_ON) - } + UpdateMoreAction.SignIn -> loginActivity.launch(null) is UpdateMoreAction.Error -> { Timber.d("UpdateMoreAction.Error intent: %s", action.intent) startActivity(action.intent) @@ -57,24 +60,7 @@ class UpdateMoreActivity : AbstractActionBarActivity() { }.exhaustive } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - // restart update process after log in - if (requestCode == REQUEST_SIGN_ON) { - if (resultCode == Activity.RESULT_OK) { - viewModel.processIntent(intent) - } else { - setResult(Activity.RESULT_CANCELED) - finish() - } - } - } - override fun onProgressCancel(requestId: Int) { viewModel.cancelProgress() } - companion object { - private const val REQUEST_SIGN_ON = 1 - } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/update/UpdateMoreViewModel.kt b/app/src/main/java/com/arcao/geocaching4locus/update/UpdateMoreViewModel.kt index 673132c9..fb8e92e9 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/update/UpdateMoreViewModel.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/update/UpdateMoreViewModel.kt @@ -13,9 +13,7 @@ import com.arcao.geocaching4locus.base.util.invoke import com.arcao.geocaching4locus.data.account.AccountManager import com.arcao.geocaching4locus.error.handler.ExceptionHandler import com.arcao.geocaching4locus.settings.manager.DefaultPreferenceManager -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.collect import locus.api.android.utils.IntentHelper import locus.api.manager.LocusMapManager import locus.api.mapper.PointMerger @@ -35,7 +33,6 @@ class UpdateMoreViewModel( val action = Command() private var job: Job? = null - @OptIn(ExperimentalCoroutinesApi::class) fun processIntent(intent: Intent) { if (locusMapManager.isLocusMapNotInstalled) { action(UpdateMoreAction.LocusMapNotInstalled) diff --git a/app/src/main/java/com/arcao/geocaching4locus/update/UpdateViewModel.kt b/app/src/main/java/com/arcao/geocaching4locus/update/UpdateViewModel.kt index c2d13302..3e0497d5 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/update/UpdateViewModel.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/update/UpdateViewModel.kt @@ -1,6 +1,6 @@ package com.arcao.geocaching4locus.update -import android.content.Context +import android.app.Application import android.content.Intent import com.arcao.geocaching4locus.R import com.arcao.geocaching4locus.authentication.util.isPremium @@ -23,12 +23,11 @@ import locus.api.android.utils.LocusUtils import locus.api.manager.LocusMapManager import locus.api.mapper.PointMerger import locus.api.mapper.Util -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point import timber.log.Timber -import java.util.Locale class UpdateViewModel( - private val context: Context, + private val context: Application, private val accountManager: AccountManager, private val defaultPreferenceManager: DefaultPreferenceManager, private val getPointFromGeocacheCode: GetPointFromGeocacheCodeUseCase, @@ -85,13 +84,9 @@ class UpdateViewModel( getPointFromGeocacheCode(updateData.geocacheCode, lite, logsCount) if (updateData.downloadLogs) { - var progress = updateData.newPoint.gcData.logs.count() + var progress = updateData.newPoint.gcData?.logs?.count() ?: 0 - logsCount = if (updateData.downloadLogs) { - AppConstants.LOGS_TO_UPDATE_MAX - } else { - defaultPreferenceManager.downloadingGeocacheLogsCount - } + logsCount = AppConstants.LOGS_TO_UPDATE_MAX updateProgress( R.string.progress_download_logs, @@ -109,7 +104,7 @@ class UpdateViewModel( it }.toList() - updateData.newPoint.gcData.logs.apply { + updateData.newPoint.gcData?.logs?.apply { addAll(logs.flatten()) sortBy { it.date @@ -180,7 +175,7 @@ class UpdateViewModel( val p = IntentHelper.getPointFromIntent(context, intent) if (p?.gcData != null) { - cacheId = p.gcData.cacheID + cacheId = p.gcData?.cacheID oldPoint = p } } catch (t: Throwable) { @@ -197,7 +192,7 @@ class UpdateViewModel( } } - if (cacheId == null || !cacheId.toUpperCase(Locale.US).startsWith("GC")) { + if (cacheId == null || !cacheId.uppercase().startsWith("GC")) { Timber.e("cacheId/simpleCacheId not found") return null } diff --git a/app/src/main/java/com/arcao/geocaching4locus/weblink/BookmarkGeocacheWebLinkViewModel.kt b/app/src/main/java/com/arcao/geocaching4locus/weblink/BookmarkGeocacheWebLinkViewModel.kt index 509b8529..ac7c6c3b 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/weblink/BookmarkGeocacheWebLinkViewModel.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/weblink/BookmarkGeocacheWebLinkViewModel.kt @@ -7,8 +7,11 @@ import com.arcao.geocaching4locus.base.usecase.GetPointFromGeocacheCodeUseCase import com.arcao.geocaching4locus.data.account.AccountManager import com.arcao.geocaching4locus.data.api.model.GeocacheType import com.arcao.geocaching4locus.error.handler.ExceptionHandler -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point import locus.api.objects.geocaching.GeocachingData +import locus.api.objects.geocaching.GeocachingData.Companion.CACHE_TYPE_COMMUNITY_CELEBRATION +import locus.api.objects.geocaching.GeocachingData.Companion.CACHE_TYPE_GC_HQ +import locus.api.objects.geocaching.GeocachingData.Companion.CACHE_TYPE_GC_HQ_CELEBRATION import java.util.Locale import java.util.regex.Pattern @@ -17,20 +20,25 @@ class BookmarkGeocacheWebLinkViewModel( getPointFromGeocacheCode: GetPointFromGeocacheCodeUseCase, exceptionHandler: ExceptionHandler, dispatcherProvider: CoroutinesDispatcherProvider -) : WebLinkViewModel(accountManager, getPointFromGeocacheCode, exceptionHandler, dispatcherProvider) { +) : WebLinkViewModel( + accountManager, + getPointFromGeocacheCode, + exceptionHandler, + dispatcherProvider +) { override val isPremiumMemberRequired: Boolean get() = true override fun isRefreshRequired(point: Point): Boolean { - return point.gcData != null && - !point.gcData.cacheUrl.isNullOrEmpty() && - getGuid(point.gcData.cacheUrl) == null + val gcData = point.gcData + return gcData != null && gcData.cacheUrl.isNotEmpty() && getGuid(gcData.cacheUrl) == null } override fun getWebLink(point: Point): Uri { - val guid = getGuid(point.gcData.cacheUrl) - val cacheType = getCacheType(point.gcData.type) + val gcData = requireNotNull(point.gcData) + val guid = getGuid(gcData.cacheUrl) + val cacheType = getCacheType(gcData.type) return if (BuildConfig.GEOCACHING_API_STAGING) { Uri.parse(String.format(Locale.ROOT, URL_FORMAT_STAGING, guid, cacheType)) @@ -53,11 +61,11 @@ class BookmarkGeocacheWebLinkViewModel( GeocachingData.CACHE_TYPE_EARTH -> return GeocacheType.EARTHCACHE GeocachingData.CACHE_TYPE_EVENT -> return GeocacheType.EVENT GeocachingData.CACHE_TYPE_GPS_ADVENTURE -> return GeocacheType.GPS_ADVENTURES_EXHIBIT - GeocachingData.CACHE_TYPE_GROUNDSPEAK -> return GeocacheType.GEOCACHING_HQ - GeocachingData.CACHE_TYPE_LF_CELEBRATION -> return GeocacheType.GEOCACHING_LOST_AND_FOUND_CELEBRATION + CACHE_TYPE_GC_HQ -> return GeocacheType.GEOCACHING_HQ + CACHE_TYPE_GC_HQ_CELEBRATION -> return GeocacheType.GEOCACHING_LOST_AND_FOUND_CELEBRATION GeocachingData.CACHE_TYPE_LETTERBOX -> return GeocacheType.LETTERBOX_HYBRID GeocachingData.CACHE_TYPE_LOCATIONLESS -> return GeocacheType.LOCATIONLESS_CACHE - GeocachingData.CACHE_TYPE_LF_EVENT -> return GeocacheType.LOST_AND_FOUND_EVENT_CACHE + CACHE_TYPE_COMMUNITY_CELEBRATION -> return GeocacheType.LOST_AND_FOUND_EVENT_CACHE GeocachingData.CACHE_TYPE_MEGA_EVENT -> return GeocacheType.MEGA_EVENT GeocachingData.CACHE_TYPE_MULTI -> return GeocacheType.MULTI_CACHE GeocachingData.CACHE_TYPE_PROJECT_APE -> return GeocacheType.PROJECT_APE @@ -72,8 +80,11 @@ class BookmarkGeocacheWebLinkViewModel( } companion object { - private const val URL_FORMAT = "https://www.geocaching.com/bookmarks/mark.aspx?guid=%s&WptTypeID=%d" - private const val URL_FORMAT_STAGING = "https://staging.geocaching.com/bookmarks/mark.aspx?guid=%s&WptTypeID=%d" - private val GUID_URL_PATTERN = Pattern.compile("guid=([a-f0-9-]+)", Pattern.CASE_INSENSITIVE) + private const val URL_FORMAT = + "https://www.geocaching.com/bookmarks/mark.aspx?guid=%s&WptTypeID=%d" + private const val URL_FORMAT_STAGING = + "https://staging.geocaching.com/bookmarks/mark.aspx?guid=%s&WptTypeID=%d" + private val GUID_URL_PATTERN = + Pattern.compile("guid=([a-f0-9-]+)", Pattern.CASE_INSENSITIVE) } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/weblink/WatchGeocacheWebLinkViewModel.kt b/app/src/main/java/com/arcao/geocaching4locus/weblink/WatchGeocacheWebLinkViewModel.kt index 75148dd9..8a9eca5e 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/weblink/WatchGeocacheWebLinkViewModel.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/weblink/WatchGeocacheWebLinkViewModel.kt @@ -7,7 +7,7 @@ import com.arcao.geocaching4locus.base.usecase.GetPointFromGeocacheCodeUseCase import com.arcao.geocaching4locus.data.account.AccountManager import com.arcao.geocaching4locus.data.api.util.ReferenceCode import com.arcao.geocaching4locus.error.handler.ExceptionHandler -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point import java.util.Locale class WatchGeocacheWebLinkViewModel( @@ -18,7 +18,8 @@ class WatchGeocacheWebLinkViewModel( ) : WebLinkViewModel(accountManager, getPointFromGeocacheCode, exceptionHandler, dispatcherProvider) { override fun getWebLink(point: Point): Uri { - val cacheId = ReferenceCode.toId(point.gcData.cacheID) + val gcData = requireNotNull(point.gcData) + val cacheId = ReferenceCode.toId(gcData.cacheID) return if (BuildConfig.GEOCACHING_API_STAGING) { Uri.parse(String.format(Locale.ROOT, URL_FORMAT_STAGING, cacheId)) diff --git a/app/src/main/java/com/arcao/geocaching4locus/weblink/WebLinkActivity.kt b/app/src/main/java/com/arcao/geocaching4locus/weblink/WebLinkActivity.kt index 9e1f8f7e..2cf1c5be 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/weblink/WebLinkActivity.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/weblink/WebLinkActivity.kt @@ -1,26 +1,30 @@ package com.arcao.geocaching4locus.weblink import android.app.Activity -import android.content.Intent import android.os.Bundle -import androidx.annotation.Nullable import com.arcao.geocaching4locus.R -import com.arcao.geocaching4locus.authentication.util.requestSignOn +import com.arcao.geocaching4locus.authentication.LoginActivity import com.arcao.geocaching4locus.base.AbstractActionBarActivity import com.arcao.geocaching4locus.base.util.exhaustive import com.arcao.geocaching4locus.base.util.showWebPage import com.arcao.geocaching4locus.base.util.withObserve -import com.arcao.geocaching4locus.data.account.AccountManager import com.arcao.geocaching4locus.error.ErrorActivity import locus.api.android.utils.IntentHelper -import org.koin.android.ext.android.inject import timber.log.Timber abstract class WebLinkActivity : AbstractActionBarActivity() { protected abstract val viewModel: WebLinkViewModel - private val accountManager by inject() - override fun onCreate(@Nullable savedInstanceState: Bundle?) { + private val loginActivity = registerForActivityResult(LoginActivity.Contract) { success -> + if (success) { + processIntent() + } else { + setResult(Activity.RESULT_CANCELED) + finish() + } + } + + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel.progress.withObserve(this, ::handleProgress) @@ -56,8 +60,7 @@ abstract class WebLinkActivity : AbstractActionBarActivity() { @Suppress("IMPLICIT_CAST_TO_ANY") private fun handleAction(action: WebLinkAction) { when (action) { - WebLinkAction.SignIn -> - accountManager.requestSignOn(this, REQUEST_SIGN_ON) + WebLinkAction.SignIn -> loginActivity.launch(null) is WebLinkAction.ShowUri -> { if (showWebPage(action.uri)) { setResult(Activity.RESULT_OK) @@ -76,28 +79,13 @@ abstract class WebLinkActivity : AbstractActionBarActivity() { finish() } WebLinkAction.PremiumMembershipRequired -> { - startActivity(ErrorActivity.IntentBuilder(this).message(R.string.error_premium_feature).build()) + startActivity( + ErrorActivity.IntentBuilder(this).message(R.string.error_premium_feature) + .build() + ) setResult(Activity.RESULT_CANCELED) finish() } }.exhaustive } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - // restart update process after log in - if (requestCode == REQUEST_SIGN_ON) { - if (resultCode == Activity.RESULT_OK) { - processIntent() - } else { - setResult(Activity.RESULT_CANCELED) - finish() - } - } - } - - companion object { - private const val REQUEST_SIGN_ON = 1 - } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/weblink/WebLinkViewModel.kt b/app/src/main/java/com/arcao/geocaching4locus/weblink/WebLinkViewModel.kt index cec0271c..e757b3ba 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/weblink/WebLinkViewModel.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/weblink/WebLinkViewModel.kt @@ -11,7 +11,7 @@ import com.arcao.geocaching4locus.base.util.invoke import com.arcao.geocaching4locus.data.account.AccountManager import com.arcao.geocaching4locus.error.handler.ExceptionHandler import kotlinx.coroutines.Job -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point abstract class WebLinkViewModel( private val accountManager: AccountManager, @@ -32,7 +32,8 @@ abstract class WebLinkViewModel( } fun resolveUri(point: Point) { - if (point.gcData == null || point.gcData.cacheID.isNullOrEmpty()) { + val gcData = point.gcData + if (gcData == null || gcData.cacheID.isEmpty()) { action(WebLinkAction.Cancel) return } @@ -57,7 +58,7 @@ abstract class WebLinkViewModel( } val newPoint = showProgress(R.string.progress_download_geocache) { - getPointFromGeocacheCode(point.gcData.cacheID) + getPointFromGeocacheCode(gcData.cacheID) } getWebLink(newPoint) } diff --git a/app/src/main/java/locus/api/android/ActionDisplayInternal.kt b/app/src/main/java/locus/api/android/ActionDisplayInternal.kt deleted file mode 100644 index 7ceb21ed..00000000 --- a/app/src/main/java/locus/api/android/ActionDisplayInternal.kt +++ /dev/null @@ -1,16 +0,0 @@ -package locus.api.android - -import android.content.Context -import android.content.Intent -import locus.api.android.utils.exceptions.RequiredVersionMissingException - -object ActionDisplayInternal { - @Throws(RequiredVersionMissingException::class) - internal fun sendData( - action: String, - context: Context, - intent: Intent, - callImport: Boolean, - center: Boolean - ): Boolean = ActionDisplay.sendData(action, context, intent, callImport, center) -} \ No newline at end of file diff --git a/app/src/main/java/locus/api/extension/LocationExt.kt b/app/src/main/java/locus/api/extension/LocationExt.kt index 2c5ecbf4..3fd1a8c9 100644 --- a/app/src/main/java/locus/api/extension/LocationExt.kt +++ b/app/src/main/java/locus/api/extension/LocationExt.kt @@ -9,5 +9,5 @@ fun Location?.isInvalid(): Boolean { contract { returns(false) implies (this@isInvalid is Location) } - return this == null || getLatitude().isNaN() || getLongitude().isNaN() + return this == null || latitude.isNaN() || longitude.isNaN() } diff --git a/app/src/main/java/locus/api/manager/LocusMapManager.kt b/app/src/main/java/locus/api/manager/LocusMapManager.kt index ab8c3087..e437d605 100644 --- a/app/src/main/java/locus/api/manager/LocusMapManager.kt +++ b/app/src/main/java/locus/api/manager/LocusMapManager.kt @@ -1,6 +1,5 @@ package locus.api.manager -import android.content.BroadcastReceiver import android.content.ClipData import android.content.Context import android.content.Intent @@ -9,18 +8,15 @@ import com.arcao.geocaching4locus.base.constants.AppConstants import com.arcao.geocaching4locus.error.exception.LocusMapRuntimeException import locus.api.android.ActionBasics import locus.api.android.ActionDisplayPoints -import locus.api.android.ActionTools import locus.api.android.objects.PackPoints import locus.api.android.utils.IntentHelper import locus.api.android.utils.LocusConst import locus.api.android.utils.LocusUtils -import locus.api.android.utils.exceptions.RequiredVersionMissingException -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point import timber.log.Timber import java.io.File import java.io.FileOutputStream import java.io.IOException -import kotlin.reflect.KClass class LocusMapManager( private val context: Context @@ -33,7 +29,10 @@ class LocusMapManager( ActionBasics.getLocusInfo(context, locusVersion)?.isPeriodicUpdatesEnabled ?: false } catch (e: Throwable) { - Timber.e(e, "Unable to receive info about periodic update state from Locus Map.") + Timber.e( + e, + "Unable to receive info about periodic update state from Locus Map." + ) return true } } else { @@ -42,10 +41,8 @@ class LocusMapManager( } val isLocusMapNotInstalled: Boolean - get() { - val lv = LocusUtils.getActiveVersion(context) - return lv == null || !lv.isVersionValid(AppConstants.LOCUS_MIN_VERSION_CODE) - } + get() = LocusUtils.getActiveVersion(context) + ?.isVersionValid(AppConstants.LOCUS_MIN_VERSION_CODE)?.not() ?: false fun createSendPointsIntent(callImport: Boolean, center: Boolean): Intent { val uri = FileProvider.getUriForFile(context, "${context.packageName}.provider", cacheFile) @@ -111,37 +108,14 @@ class LocusMapManager( * @param packName name of pack * @throws LocusMapRuntimeException exception in case ane exception occurs while Locus Map API calls */ - @Throws(RequiredVersionMissingException::class) fun removePackFromLocus(packName: String) { try { - // create empty pack - val pw = PackPoints(packName) - - // create and send intent - val intent = Intent() - intent.putExtra(LocusConst.INTENT_EXTRA_POINTS_DATA, pw.asBytes) - ActionDisplayPoints.sendPackSilent(context, pw, false) + ActionDisplayPoints.removePackFromLocus(context, packName) } catch (t: Throwable) { throw LocusMapRuntimeException(t) } } - // make sure Live Map broadcast receiver is always enabled - fun enablePeriodicUpdatesReceiver(clazz: KClass) { - try { - val locusVersion = LocusUtils.getActiveVersion(context) - if (locusVersion != null) { - ActionTools.enablePeriodicUpdatesReceiver( - context, - locusVersion, - clazz.java - ) - } - } catch (e: Throwable) { - Timber.e(e, "Unable to enable ${clazz.java.simpleName}.") - } - } - @Throws(LocusMapRuntimeException::class) fun sendPointsSilent(packName: String, points: List, centerOnData: Boolean = false) { try { diff --git a/app/src/main/java/locus/api/mapper/DataMapper.kt b/app/src/main/java/locus/api/mapper/DataMapper.kt index 9ed2e9e8..ab770972 100644 --- a/app/src/main/java/locus/api/mapper/DataMapper.kt +++ b/app/src/main/java/locus/api/mapper/DataMapper.kt @@ -3,7 +3,7 @@ package locus.api.mapper import com.arcao.geocaching4locus.data.api.model.Geocache import com.arcao.geocaching4locus.data.api.model.GeocacheLog import com.arcao.geocaching4locus.data.api.model.Trackable -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point import locus.api.utils.addIgnoreNull class DataMapper( diff --git a/app/src/main/java/locus/api/mapper/GeocacheConverter.kt b/app/src/main/java/locus/api/mapper/GeocacheConverter.kt index 4395daf1..808d2bda 100644 --- a/app/src/main/java/locus/api/mapper/GeocacheConverter.kt +++ b/app/src/main/java/locus/api/mapper/GeocacheConverter.kt @@ -15,9 +15,10 @@ import com.arcao.geocaching4locus.data.api.util.ReferenceCode import com.arcao.geocaching4locus.settings.manager.DefaultPreferenceManager import locus.api.mapper.Util.applyUnavailabilityForGeocache import locus.api.objects.extra.Location -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point import locus.api.objects.geocaching.GeocachingAttribute import locus.api.objects.geocaching.GeocachingData +import locus.api.objects.geocaching.GeocachingImage import timber.log.Timber import java.text.ParseException import java.util.regex.Pattern @@ -31,9 +32,10 @@ class GeocacheConverter( private val waypointConverter: WaypointConverter ) { fun createLocusPoint(cache: Geocache): Point { - val loc = Location() - .setLatitude(cache.postedCoordinates?.latitude ?: throw IllegalArgumentException("Coordinates missing")) - .setLongitude(cache.postedCoordinates?.longitude ?: throw IllegalArgumentException("Coordinates missing")) + val loc = Location().apply { + latitude = requireNotNull(cache.postedCoordinates?.latitude) { "Coordinates missing" } + longitude = requireNotNull(cache.postedCoordinates?.longitude) { "Coordinates missing" } + } val p = Point(cache.name, loc).apply { gcData = GeocachingData().apply { @@ -44,12 +46,12 @@ class GeocacheConverter( type = cache.geocacheType.toLocusMapGeocacheType() difficulty = cache.difficulty ?: 1F terrain = cache.terrain ?: 1F - owner = cache.owner?.username ?: cache.ownerAlias - placedBy = cache.ownerAlias + owner = cache.owner?.username ?: cache.ownerAlias.orEmpty() + placedBy = cache.ownerAlias.orEmpty() isAvailable = cache.status == GeocacheStatus.ACTIVE isArchived = cache.status == GeocacheStatus.ARCHIVED isPremiumOnly = cache.isPremiumOnly ?: false - cacheUrl = cache.url + cacheUrl = cache.url.orEmpty() dateHidden = cache.placedDateInstant?.toEpochMilli() ?: 0 datePublished = cache.publishedDateInstant?.toEpochMilli() ?: 0 @@ -58,19 +60,23 @@ class GeocacheConverter( container = cache.geocacheSize.getLocusMapGeocacheSize() isFound = cache.userData?.foundDate != null - country = cache.location?.country - state = cache.location?.state + country = cache.location?.country.orEmpty() + state = cache.location?.state.orEmpty() setDescriptions( - BadBBCodeFixer.fix(cache.shortDescription), cache.containsHtml ?: false, - BadBBCodeFixer.fix(cache.longDescription), cache.containsHtml ?: false + BadBBCodeFixer.fix(cache.shortDescription).orEmpty(), + cache.containsHtml ?: false, + BadBBCodeFixer.fix(cache.longDescription).orEmpty(), + cache.containsHtml ?: false ) - encodedHints = cache.hints - notes = cache.userData?.note + encodedHints = cache.hints.orEmpty() + notes = cache.userData?.note.orEmpty() favoritePoints = cache.favoritePoints ?: 0 - for (image in cache.images.orEmpty()) { - addImage(imageDataConverter.createLocusGeocachingImage(image)) + images = mutableListOf().apply { + for (image in cache.images.orEmpty()) { + imageDataConverter.createLocusGeocachingImage(image)?.let(this::add) + } } for (attribute in cache.attributes.orEmpty()) { @@ -88,7 +94,10 @@ class GeocacheConverter( updateGeocacheLocationByCorrectedCoordinates(p, cache) if (defaultPreferenceManager.disableDnfNmNaGeocaches) - applyUnavailabilityForGeocache(p, defaultPreferenceManager.disableDnfNmNaGeocachesThreshold) + applyUnavailabilityForGeocache( + p, + defaultPreferenceManager.disableDnfNmNaGeocachesThreshold + ) return p } @@ -99,12 +108,12 @@ class GeocacheConverter( GeocacheType.EARTHCACHE -> GeocachingData.CACHE_TYPE_EARTH GeocacheType.EVENT -> GeocachingData.CACHE_TYPE_EVENT GeocacheType.GPS_ADVENTURES_EXHIBIT -> GeocachingData.CACHE_TYPE_GPS_ADVENTURE - GeocacheType.GEOCACHING_BLOCK_PARTY -> GeocachingData.CACHE_TYPE_GROUNDSPEAK - GeocacheType.GEOCACHING_HQ -> GeocachingData.CACHE_TYPE_GROUNDSPEAK - GeocacheType.GEOCACHING_LOST_AND_FOUND_CELEBRATION -> GeocachingData.CACHE_TYPE_LF_CELEBRATION + GeocacheType.GEOCACHING_BLOCK_PARTY -> GeocachingData.CACHE_TYPE_GC_HQ + GeocacheType.GEOCACHING_HQ -> GeocachingData.CACHE_TYPE_GC_HQ + GeocacheType.GEOCACHING_LOST_AND_FOUND_CELEBRATION -> GeocachingData.CACHE_TYPE_GC_HQ_CELEBRATION GeocacheType.LETTERBOX_HYBRID -> GeocachingData.CACHE_TYPE_LETTERBOX GeocacheType.LOCATIONLESS_CACHE -> GeocachingData.CACHE_TYPE_LOCATIONLESS - GeocacheType.LOST_AND_FOUND_EVENT_CACHE -> GeocachingData.CACHE_TYPE_LF_EVENT + GeocacheType.LOST_AND_FOUND_EVENT_CACHE -> GeocachingData.CACHE_TYPE_COMMUNITY_CELEBRATION GeocacheType.MEGA_EVENT -> GeocachingData.CACHE_TYPE_MEGA_EVENT GeocacheType.MULTI_CACHE -> GeocachingData.CACHE_TYPE_MULTI GeocacheType.PROJECT_APE -> GeocachingData.CACHE_TYPE_PROJECT_APE @@ -124,7 +133,7 @@ class GeocacheConverter( GeocacheSize.MICRO -> GeocachingData.CACHE_SIZE_MICRO GeocacheSize.NOT_CHOSEN -> GeocachingData.CACHE_SIZE_NOT_CHOSEN GeocacheSize.OTHER -> GeocachingData.CACHE_SIZE_OTHER - GeocacheSize.MEDIUM -> GeocachingData.CACHE_SIZE_REGULAR + GeocacheSize.REGULAR -> GeocachingData.CACHE_SIZE_REGULAR GeocacheSize.SMALL -> GeocachingData.CACHE_SIZE_SMALL else -> GeocachingData.CACHE_SIZE_OTHER } @@ -137,17 +146,18 @@ class GeocacheConverter( val location = p.location - p.gcData.apply { + p.gcData?.apply { isComputed = true - latOriginal = location.getLatitude() - lonOriginal = location.getLongitude() + latOriginal = location.latitude + lonOriginal = location.longitude } // update coordinates to new location location.set( - Location() - .setLatitude(correctedCoordinates.latitude) - .setLongitude(correctedCoordinates.longitude) + Location().apply { + latitude = correctedCoordinates.latitude + longitude = correctedCoordinates.longitude + } ) } @@ -239,9 +249,11 @@ class GeocacheConverter( companion object { private val WAYPOINT_BASE_ID = ReferenceCode.base31Decode("N0") - private val FINAL_WAYPOINT_NAME_PATTERN = Pattern.compile("fin[a|á]+[l|ł]", Pattern.CASE_INSENSITIVE) + private val FINAL_WAYPOINT_NAME_PATTERN = + Pattern.compile("fin[a|á]+[l|ł]", Pattern.CASE_INSENSITIVE) - private val NOTE_COORDINATE_PATTERN = Pattern.compile("\\b[nNsS]\\s*\\d") // begin of coordinates + private val NOTE_COORDINATE_PATTERN = + Pattern.compile("\\b[nNsS]\\s*\\d") // begin of coordinates private val NOTE_NAME_PATTERN = Pattern.compile("^(.+):\\s*\\z") } } diff --git a/app/src/main/java/locus/api/mapper/GeocacheLogConverter.kt b/app/src/main/java/locus/api/mapper/GeocacheLogConverter.kt index d7607f1a..cddba544 100644 --- a/app/src/main/java/locus/api/mapper/GeocacheLogConverter.kt +++ b/app/src/main/java/locus/api/mapper/GeocacheLogConverter.kt @@ -2,7 +2,7 @@ package locus.api.mapper import com.arcao.geocaching4locus.data.api.model.GeocacheLog import com.arcao.geocaching4locus.data.api.model.GeocacheLogType -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point import locus.api.objects.geocaching.GeocachingLog import locus.api.utils.addIgnoreNull @@ -15,7 +15,7 @@ class GeocacheLogConverter( return for (log in logs) { - point.gcData.logs.addIgnoreNull(createLocusGeocachingLog(log)) + point.gcData?.logs?.addIgnoreNull(createLocusGeocachingLog(log)) } sortLocusGeocachingLogsByDate(point) @@ -29,18 +29,18 @@ class GeocacheLogConverter( return GeocachingLog().apply { id = log.id date = log.loggedDateInstant?.toEpochMilli() ?: 0 - logText = log.text + logText = log.text.orEmpty() type = log.geocacheLogType.toLocusMapLogType() val author = log.owner if (author != null) { - finder = author.username + finder = author.username.orEmpty() findersFound = author.findCount findersId = author.id } for (image in log.images ?: emptyList()) { - addImage(imageDataConverter.createLocusGeocachingImage(image)) + imageDataConverter.createLocusGeocachingImage(image)?.let(this::addImage) } log.updatedCoordinates?.let { coordinates -> cooLat = coordinates.latitude @@ -61,6 +61,7 @@ class GeocacheLogConverter( GeocacheLogType.OWNER_MAINTENANCE -> GeocachingLog.CACHE_LOG_TYPE_OWNER_MAINTENANCE GeocacheLogType.POST_REVIEWER_NOTE -> GeocachingLog.CACHE_LOG_TYPE_POST_REVIEWER_NOTE GeocacheLogType.PUBLISH_LISTING -> GeocachingLog.CACHE_LOG_TYPE_PUBLISH_LISTING + GeocacheLogType.RETRACT_LISTING -> GeocachingLog.CACHE_LOG_TYPE_RETRACT_LISTING GeocacheLogType.TEMPORARILY_DISABLE_LISTING -> GeocachingLog.CACHE_LOG_TYPE_TEMPORARILY_DISABLE_LISTING GeocacheLogType.UPDATE_COORDINATES -> GeocachingLog.CACHE_LOG_TYPE_UPDATE_COORDINATES GeocacheLogType.WEBCAM_PHOTO_TAKEN -> GeocachingLog.CACHE_LOG_TYPE_WEBCAM_PHOTO_TAKEN @@ -68,6 +69,7 @@ class GeocacheLogConverter( GeocacheLogType.WRITE_NOTE -> GeocachingLog.CACHE_LOG_TYPE_WRITE_NOTE GeocacheLogType.ARCHIVE -> GeocachingLog.CACHE_LOG_TYPE_ARCHIVE GeocacheLogType.UNARCHIVE -> GeocachingLog.CACHE_LOG_TYPE_UNARCHIVE + GeocacheLogType.SUBMIT_FOR_REVIEW -> GeocachingLog.CACHE_LOG_TYPE_WRITE_NOTE else -> GeocachingLog.CACHE_LOG_TYPE_WRITE_NOTE } } @@ -76,7 +78,6 @@ class GeocacheLogConverter( if (waypoint.gcData?.logs?.isEmpty() != false) return - // Note: Long.compareTo was introduced in API 19 - waypoint.gcData.logs.sortWith(Comparator { lhs, rhs -> if (lhs.date < rhs.date) 1 else if (lhs.date == rhs.date) 0 else -1 }) + waypoint.gcData?.logs?.sortWith { lhs, rhs -> lhs.date.compareTo(rhs.date) } } } diff --git a/app/src/main/java/locus/api/mapper/ImageDataConverter.kt b/app/src/main/java/locus/api/mapper/ImageDataConverter.kt index 95a36271..7f38a7ab 100644 --- a/app/src/main/java/locus/api/mapper/ImageDataConverter.kt +++ b/app/src/main/java/locus/api/mapper/ImageDataConverter.kt @@ -11,9 +11,9 @@ class ImageDataConverter { return null return GeocachingImage().apply { - name = imageData.description - description = imageData.description - thumbUrl = imageData.thumbnailUrl + name = imageData.description.orEmpty() + description = imageData.description.orEmpty() + thumbUrl = imageData.thumbnailUrl.orEmpty() url = imageData.url } } diff --git a/app/src/main/java/locus/api/mapper/PointMerger.kt b/app/src/main/java/locus/api/mapper/PointMerger.kt index bed96561..7c8bfa33 100644 --- a/app/src/main/java/locus/api/mapper/PointMerger.kt +++ b/app/src/main/java/locus/api/mapper/PointMerger.kt @@ -3,7 +3,7 @@ package locus.api.mapper import com.arcao.geocaching4locus.settings.manager.DefaultPreferenceManager import locus.api.mapper.Util.GSAK_USERNAME import locus.api.mapper.Util.applyUnavailabilityForGeocache -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point import locus.api.objects.geocaching.GeocachingLog class PointMerger( @@ -12,11 +12,11 @@ class PointMerger( fun mergePoints(dest: Point, src: Point?) { dest.removeExtraOnDisplay() - if (src?.gcData == null) - return + val srcGcData = src?.gcData ?: return + val destGcData = dest.gcData ?: return copyArchivedGeocacheLocation(dest, src) - copyGsakGeocachingLogs(dest.gcData.logs, src.gcData.logs) + copyGsakGeocachingLogs(destGcData.logs, srcGcData.logs) copyComputedCoordinates(dest, src) copyPointId(dest, src) copyGcVote(dest, src) @@ -33,20 +33,26 @@ class PointMerger( return // store original logs - val originalLogs = ArrayList(dest.gcData.logs) + val originalLogs = ArrayList(dest.gcData?.logs.orEmpty()) // replace logs with new one - dest.gcData.logs.apply { + dest.gcData?.logs?.apply { clear() - addAll(src.gcData.logs) + src.gcData?.logs?.let(this::addAll) } // copy GSAK logs from original logs - copyGsakGeocachingLogs(dest.gcData.logs, originalLogs) + copyGsakGeocachingLogs(dest.gcData?.logs, originalLogs) } // issue #14: Keep cache logs from GSAK when updating cache - private fun copyGsakGeocachingLogs(dest: MutableList, src: List) { + private fun copyGsakGeocachingLogs( + dest: MutableList?, + src: List? + ) { + src ?: return + dest ?: return + for (fromLog in src.reversed()) { if (GSAK_USERNAME.equals(fromLog.finder, ignoreCase = true)) { fromLog.date = System.currentTimeMillis() @@ -57,40 +63,40 @@ class PointMerger( // issue #13: Use old coordinates when cache is archived after update private fun copyArchivedGeocacheLocation(dest: Point, src: Point) { - if (src.gcData == null || dest.gcData == null) - return + val srcGcData = src.gcData ?: return + val destGcData = dest.gcData ?: return - val latitude = src.location.getLatitude() - val longitude = src.location.getLongitude() + val latitude = src.location.latitude + val longitude = src.location.longitude // are valid coordinates - if (java.lang.Double.isNaN(latitude) || java.lang.Double.isNaN(longitude) || latitude == 0.0 && longitude == 0.0) + if (latitude.isNaN() || longitude.isNaN() || latitude == 0.0 && longitude == 0.0) return // is new point not archived or has computed coordinates - if (!dest.gcData.isArchived || src.gcData.isComputed) + if (!destGcData.isArchived || srcGcData.isComputed) return // store coordinates to new point dest.location.apply { - setLatitude(latitude) - setLongitude(longitude) + this.latitude = latitude + this.longitude = longitude } } // Copy computed coordinates to new point private fun copyComputedCoordinates(dest: Point, src: Point) { - if (src.gcData == null || dest.gcData == null) - return + val srcGcData = src.gcData ?: return + val destGcData = dest.gcData ?: return - if (!src.gcData.isComputed || dest.gcData.isComputed) + if (!srcGcData.isComputed || destGcData.isComputed) return val location = dest.location - dest.gcData.apply { - latOriginal = location.getLatitude() - lonOriginal = location.getLongitude() + destGcData.apply { + latOriginal = location.latitude + lonOriginal = location.longitude isComputed = true } @@ -99,33 +105,36 @@ class PointMerger( } private fun copyPointId(dest: Point, src: Point) { - if (src.getId() == 0L) + if (src.id == 0L) return - dest.setId(src.getId()) + dest.id = src.id } private fun copyGcVote(dest: Point, src: Point) { - if (src.gcData == null || dest.gcData == null) - return + val srcGcData = src.gcData ?: return + val destGcData = dest.gcData ?: return - dest.gcData.apply { - gcVoteAverage = src.gcData.gcVoteAverage - gcVoteNumOfVotes = src.gcData.gcVoteNumOfVotes - gcVoteUserVote = src.gcData.gcVoteUserVote + destGcData.apply { + gcVoteAverage = srcGcData.gcVoteAverage + gcVoteNumOfVotes = srcGcData.gcVoteNumOfVotes + gcVoteUserVote = srcGcData.gcVoteUserVote } } private fun copyEditedGeocachingWaypointLocation(dest: Point, src: Point) { - if (dest.gcData?.waypoints?.isEmpty() != false || src.gcData.waypoints.isEmpty()) + val destWaypoints = dest.gcData?.waypoints ?: return + val srcWaypoints = src.gcData?.waypoints ?: return + + if (destWaypoints.isEmpty() || srcWaypoints.isEmpty()) return // find Waypoint with zero coordinates - for (waypoint in dest.gcData.waypoints) { + for (waypoint in destWaypoints) { if (waypoint.lat == 0.0 && waypoint.lon == 0.0) { // replace with coordinates from src Waypoint - for (fromWaypoint in src.gcData.waypoints) { + for (fromWaypoint in srcWaypoints) { if (waypoint.code.equals(fromWaypoint.code, ignoreCase = true)) { waypoint.apply { diff --git a/app/src/main/java/locus/api/mapper/TrackableConverter.kt b/app/src/main/java/locus/api/mapper/TrackableConverter.kt index b27ef851..6b6b168e 100644 --- a/app/src/main/java/locus/api/mapper/TrackableConverter.kt +++ b/app/src/main/java/locus/api/mapper/TrackableConverter.kt @@ -1,15 +1,17 @@ package locus.api.mapper import com.arcao.geocaching4locus.data.api.model.Trackable -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point import locus.api.objects.geocaching.GeocachingTrackable class TrackableConverter { fun addTrackables(point: Point, trackables: Collection) { - if (point.gcData?.trackables == null || trackables.isEmpty()) + val gcData = point.gcData ?: return + + if (trackables.isEmpty()) return - point.gcData.trackables.addAll(createLocusGeocachingTrackables(trackables)) + gcData.trackables.addAll(createLocusGeocachingTrackables(trackables)) } fun createLocusGeocachingTrackables(trackables: Collection): Collection { @@ -25,8 +27,8 @@ class TrackableConverter { id = trackable.id imgUrl = trackable.iconUrl.orEmpty() name = trackable.name - originalOwner = trackable.owner.username - currentOwner = trackable.owner.username + originalOwner = trackable.owner.username.orEmpty() + currentOwner = trackable.owner.username.orEmpty() srcDetails = trackable.url.orEmpty() released = trackable.releasedDate.toEpochMilli() origin = trackable.originCountry.orEmpty() diff --git a/app/src/main/java/locus/api/mapper/Util.kt b/app/src/main/java/locus/api/mapper/Util.kt index ec5c8594..6ce028f1 100644 --- a/app/src/main/java/locus/api/mapper/Util.kt +++ b/app/src/main/java/locus/api/mapper/Util.kt @@ -1,7 +1,7 @@ package locus.api.mapper import androidx.annotation.NonNull -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point import locus.api.objects.geocaching.GeocachingLog object Util { @@ -10,16 +10,18 @@ object Util { fun applyUnavailabilityForGeocache(@NonNull point: Point, threshold: Int) { var counter = 0 + val gcData = point.gcData ?: return + // only when there is any log - if (point.gcData?.logs?.isEmpty() != false) + if (gcData.logs.isEmpty()) return // skip analyzing already archived geocache - if (point.gcData.isArchived) + if (gcData.isArchived) return // go through all logs (must be sorted by visited date, newest first) - val geocachingLogs = point.gcData.logs + val geocachingLogs = gcData.logs loop@ for (log in geocachingLogs) { // skip GSAK log @@ -40,7 +42,7 @@ object Util { // if counter contains required threshold if (counter >= threshold) { // set geocache as not available - point.gcData.isAvailable = false + gcData.isAvailable = false } } } diff --git a/app/src/main/java/locus/api/mapper/WaypointConverter.kt b/app/src/main/java/locus/api/mapper/WaypointConverter.kt index 555f9b7e..4f2dd572 100644 --- a/app/src/main/java/locus/api/mapper/WaypointConverter.kt +++ b/app/src/main/java/locus/api/mapper/WaypointConverter.kt @@ -3,30 +3,30 @@ package locus.api.mapper import com.arcao.geocaching4locus.data.api.model.AdditionalWaypoint import com.arcao.geocaching4locus.data.api.model.enums.AdditionalWaypointType import com.arcao.geocaching4locus.data.api.util.ReferenceCode -import locus.api.objects.extra.Point +import locus.api.objects.geoData.Point import locus.api.objects.geocaching.GeocachingWaypoint -import locus.api.utils.addIgnoreNull import locus.api.utils.isNullOrEmpty class WaypointConverter { fun addWaypoints(point: Point, waypoints: Collection?, geocacheId: Long) { - if (point.gcData?.waypoints == null || waypoints.isNullOrEmpty()) + val pointWaypoints = point.gcData?.waypoints ?: return + + if (waypoints.isNullOrEmpty()) return for (waypoint in waypoints) { - point.gcData.waypoints.addIgnoreNull(createLocusGeocachingWaypoint(waypoint, geocacheId)) + createLocusGeocachingWaypoint(waypoint, geocacheId)?.let(pointWaypoints::add) } } private fun createLocusGeocachingWaypoint(waypoint: AdditionalWaypoint?, geocacheId: Long): GeocachingWaypoint? { - if (waypoint == null) - return null + waypoint ?: return null return GeocachingWaypoint().apply { code = ReferenceCode.toReferenceCode(waypoint.prefix, geocacheId) lat = waypoint.coordinates?.latitude ?: 0.0 lon = waypoint.coordinates?.longitude ?: 0.0 - desc = waypoint.description + desc = waypoint.description.orEmpty() name = waypoint.name type = waypoint.type.toLocusMapWaypointType() } diff --git a/app/src/main/java/org/oshkimaadziig/george/androidutils/SpanFormatter.kt b/app/src/main/java/org/oshkimaadziig/george/androidutils/SpanFormatter.kt index 9ef275ba..8544fd57 100644 --- a/app/src/main/java/org/oshkimaadziig/george/androidutils/SpanFormatter.kt +++ b/app/src/main/java/org/oshkimaadziig/george/androidutils/SpanFormatter.kt @@ -20,7 +20,6 @@ import android.text.SpannableString import android.text.SpannableStringBuilder import android.text.Spanned import android.text.SpannedString -import androidx.annotation.NonNull import java.util.Locale import java.util.regex.Pattern @@ -30,7 +29,8 @@ import java.util.regex.Pattern * @author George T. Steel */ object SpanFormatter { - private val FORMAT_SEQUENCE = Pattern.compile("%([0-9]+\\$| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/btn_dashboard_bg.xml b/app/src/main/res/drawable/btn_dashboard_bg.xml index bc6ed12c..127f4752 100644 --- a/app/src/main/res/drawable/btn_dashboard_bg.xml +++ b/app/src/main/res/drawable/btn_dashboard_bg.xml @@ -1,24 +1,31 @@ - - - - + + - - - - - + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_dashboard.xml b/app/src/main/res/layout/activity_dashboard.xml index 588c7ab8..db7913f2 100644 --- a/app/src/main/res/layout/activity_dashboard.xml +++ b/app/src/main/res/layout/activity_dashboard.xml @@ -2,109 +2,110 @@ + + name="vm" + type="com.arcao.geocaching4locus.dashboard.DashboardViewModel" /> + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:context=".dashboard.DashboardActivity"> + android:id="@+id/toolbar" + layout="@layout/toolbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + android:id="@+id/db_search_nearest" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:drawableTop="@drawable/ic_action_search" + android:onClick="@{() -> vm.onClickSearchNearest()}" + android:text="@string/menu_nearest" + app:layout_constraintEnd_toStartOf="@+id/db_import_gc" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/toolbar" /> + android:id="@+id/db_import_gc" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:drawableTop="@drawable/ic_action_gc_input" + android:onClick="@{() -> vm.onClickImportGcCode()}" + android:text="@string/menu_import_from_gc" + app:layout_constraintEnd_toStartOf="@+id/db_import_bookmark" + app:layout_constraintStart_toEndOf="@+id/db_search_nearest" + app:layout_constraintTop_toTopOf="@+id/db_search_nearest" /> + android:id="@+id/db_import_bookmark" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:drawableTop="@drawable/ic_action_import_bookmark" + android:enabled="@{vm.premium}" + android:onClick="@{() -> vm.onClickImportBookmarks()}" + android:text="@{vm.premium ? @string/menu_import_bookmarks : @string/format_premium(@string/menu_import_bookmarks)}" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/db_import_gc" + app:layout_constraintTop_toTopOf="@+id/db_search_nearest" + tools:text="@string/menu_import_bookmarks" /> + android:id="@+id/db_live_map" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:checked="@{safeUnbox(vm.liveMapEnabled)}" + android:drawableTop="@drawable/ic_action_live_map" + android:onClick="@{() -> vm.onClickLiveMap()}" + android:text="@string/menu_live_map" + app:layout_constraintEnd_toEndOf="@+id/db_search_nearest" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/db_search_nearest" + app:toggleable="true" /> + android:id="@+id/db_live_map_download_caches" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:drawableTop="@drawable/ic_action_live_map_download_caches" + android:enabled="@{safeUnbox(vm.liveMapEnabled)}" + android:onClick="@{() -> vm.onClickImportLiveMapGc()}" + android:text="@string/menu_live_map_download_caches" + app:layout_constraintEnd_toStartOf="@+id/db_import_bookmark" + app:layout_constraintStart_toEndOf="@+id/db_search_nearest" + app:layout_constraintTop_toBottomOf="@+id/db_search_nearest" /> + android:id="@+id/db_preferences" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:drawableTop="@drawable/ic_action_settings" + android:onClick="@{() -> vm.onClickPreferences()}" + android:text="@string/menu_settings" + app:layout_constraintEnd_toStartOf="@+id/db_live_map_download_caches" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/db_live_map" /> + android:id="@+id/db_users_guide" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:drawableTop="@drawable/ic_action_help" + android:onClick="@{() -> vm.onClickUsersGuide()}" + android:text="@string/menu_users_guide" + app:layout_constraintEnd_toEndOf="@+id/db_live_map_download_caches" + app:layout_constraintStart_toStartOf="@+id/db_live_map_download_caches" + app:layout_constraintTop_toTopOf="@+id/db_preferences" /> diff --git a/app/src/main/res/layout/activity_import_bookmark.xml b/app/src/main/res/layout/activity_import_bookmark.xml index 6bffb447..b857c0eb 100644 --- a/app/src/main/res/layout/activity_import_bookmark.xml +++ b/app/src/main/res/layout/activity_import_bookmark.xml @@ -1,16 +1,19 @@ + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:minHeight="400dp" + android:orientation="vertical" + tools:context=".import_bookmarks.ImportBookmarkActivity"> - + - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index e4c50864..8a4131d1 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -6,9 +6,12 @@ android:orientation="vertical" tools:context=".settings.SettingsActivity"> - + - diff --git a/app/src/main/res/layout/fragment_bookmark.xml b/app/src/main/res/layout/fragment_bookmark.xml index db9ea06f..2504ba17 100644 --- a/app/src/main/res/layout/fragment_bookmark.xml +++ b/app/src/main/res/layout/fragment_bookmark.xml @@ -10,6 +10,14 @@ + + + + + android:visibility="@{isLoading ? View.GONE : View.VISIBLE}"> + android:visibility="@{isEmpty ? View.VISIBLE : View.GONE}" /> + xmlns:tools="http://schemas.android.com/tools"> - + + name="vm" + type="com.arcao.geocaching4locus.import_bookmarks.fragment.BookmarkListViewModel" /> + + + + + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/card_window_background" + tools:context="com.arcao.geocaching4locus.import_bookmarks.ImportBookmarkActivity"> + android:id="@+id/progressContainer" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:orientation="vertical" + android:visibility="@{isLoading ? View.VISIBLE : View.GONE}"> + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + android:id="@+id/listContainer" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="@{isLoading ? View.GONE : View.VISIBLE}"> + android:id="@+id/list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scrollbarStyle="outsideOverlay" + android:scrollbars="vertical" + tools:listitem="@layout/view_bookmark_list_item" /> + android:id="@+id/textEmpty" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:visibility="@{isEmpty ? View.VISIBLE : View.GONE}" /> diff --git a/app/src/main/res/layout/toolbar.xml b/app/src/main/res/layout/toolbar.xml index 37a4cbd8..363dd873 100644 --- a/app/src/main/res/layout/toolbar.xml +++ b/app/src/main/res/layout/toolbar.xml @@ -1,11 +1,9 @@ - \ No newline at end of file + android:minHeight="?attr/actionBarSize" + app:popupTheme="@style/ThemeOverlay.AppCompat.Light" + app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> \ No newline at end of file diff --git a/app/src/main/res/layout/view_bookmark_list_item.xml b/app/src/main/res/layout/view_bookmark_list_item.xml index 21fc47c1..f61b054a 100644 --- a/app/src/main/res/layout/view_bookmark_list_item.xml +++ b/app/src/main/res/layout/view_bookmark_list_item.xml @@ -92,7 +92,6 @@ app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/button" app:layout_constraintTop_toBottomOf="@+id/divider" - tools:targetApi="jelly_bean" tools:text="3 geocaches"/>