From 761ada42d3dc9628ed0454c99e246aa7b7ae1d66 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Mon, 6 Dec 2021 10:48:25 -0800 Subject: [PATCH 01/23] Update Project Dependencies --- build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 5242e2cf..1e9aa80f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.4.21' + ext.kotlin_version = '1.6.0' repositories { google() jcenter() @@ -9,9 +9,9 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:4.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.2" - classpath 'com.google.gms:google-services:4.3.4' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1' + classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5" + classpath 'com.google.gms:google-services:4.3.10' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1' classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:1.2.0" } From c0f7a060ed11ca2b1d176d1de82121fddf0829d6 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Mon, 6 Dec 2021 10:49:00 -0800 Subject: [PATCH 02/23] Update App Dependencies --- app/build.gradle | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b35c4c1d..6ca3db07 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -87,37 +87,37 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation 'com.google.android.material:material:1.1.0' + implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.activity:activity-ktx:1.1.0' implementation 'androidx.fragment:fragment-ktx:1.2.5' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0' implementation 'com.google.android.gms:play-services-ads:20.2.0' - implementation 'com.google.android.gms:play-services-location:17.1.0' + implementation 'com.google.android.gms:play-services-location:18.0.0' implementation 'com.google.android.gms:play-services-maps:17.0.0' - implementation 'com.google.firebase:firebase-core:18.0.2' - implementation 'com.google.firebase:firebase-analytics:18.0.2' - implementation 'com.google.firebase:firebase-messaging:21.0.1' - implementation 'com.google.firebase:firebase-crashlytics-ktx:17.3.1' + implementation 'com.google.firebase:firebase-core:20.0.0' + implementation 'com.google.firebase:firebase-analytics:20.0.0' + implementation 'com.google.firebase:firebase-messaging:23.0.0' + implementation 'com.google.firebase:firebase-crashlytics-ktx:18.2.5' implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.preference:preference-ktx:1.1.1' - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0" implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.constraintlayout:constraintlayout:2.1.2' - kapt "androidx.lifecycle:lifecycle-common-java8:2.2.0" + kapt "androidx.lifecycle:lifecycle-common-java8:2.4.0" implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation "androidx.room:room-runtime:2.2.6" - kapt "androidx.room:room-compiler:2.2.6" - annotationProcessor 'androidx.room:room-compiler:2.2.6' + implementation "androidx.room:room-runtime:2.3.0" + kapt "androidx.room:room-compiler:2.3.0" + annotationProcessor 'androidx.room:room-compiler:2.3.0' // drag and drop list implementation 'com.ernestoyaquello.dragdropswiperecyclerview:drag-drop-swipe-recyclerview:0.5.1' @@ -167,12 +167,12 @@ dependencies { // Required -- JUnit 4 framework testImplementation 'junit:junit:4.13.1' // Optional -- Robolectric environment - testImplementation 'androidx.test:core:1.3.0' - androidTestImplementation 'androidx.test:core:1.3.0' - testImplementation "androidx.room:room-testing:2.2.6" + testImplementation 'androidx.test:core:1.4.0' + androidTestImplementation 'androidx.test:core:1.4.0' + testImplementation "androidx.room:room-testing:2.3.0" androidTestImplementation "androidx.arch.core:core-testing:2.1.0" - androidTestImplementation 'androidx.test:runner:1.3.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } From 5685d1c768ed582039d5a309353dd19c31aff4a8 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Mon, 6 Dec 2021 10:49:34 -0800 Subject: [PATCH 03/23] Update SDK --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6ca3db07..0da71d90 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,11 +14,11 @@ apply plugin: "androidx.navigation.safeargs.kotlin" apply plugin: 'com.google.firebase.crashlytics' android { - compileSdkVersion 30 + compileSdkVersion 31 defaultConfig { applicationId "gov.wa.wsdot.android.wsdot" minSdkVersion 21 - targetSdkVersion 30 + targetSdkVersion 31 versionCode 21060703 versionName "6.7.3" vectorDrawables.useSupportLibrary = true From c24c5d48c220e66cf860c903a59c112733fd67aa Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Mon, 6 Dec 2021 10:51:31 -0800 Subject: [PATCH 04/23] Android 12 export declaration Android 12 requires explicit declaration of export attribute for activities, services, or broadcast receivers. For components that include the LAUNCHER category, set android:exported to true. --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e7868335..74263ef8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -51,6 +51,7 @@ From 64ab19abb8b73ce37ded3e3d579b25555eae7399 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Mon, 6 Dec 2021 11:11:47 -0800 Subject: [PATCH 05/23] Update Google Map Dependency Project would not compile after Google Map Dependency update. Type mismatch error in onMapReady override function. Removed null value assignment from non-nullable variables to fix type mismatch error. --- app/build.gradle | 2 +- .../gov/wa/wsdot/android/wsdot/ui/cameras/CameraFragment.kt | 2 +- .../android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt | 2 +- .../android/wsdot/ui/highwayAlerts/HighwayAlertFragment.kt | 2 +- .../android/wsdot/ui/tollrates/tollsigns/TollTripFragment.kt | 2 +- .../wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt | 2 +- .../android/wsdot/ui/trafficmap/restareas/RestAreaFragment.kt | 2 +- .../travelerinformation/bridgeAlerts/BridgeAlertFragment.kt | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0da71d90..65cd4cc7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -95,7 +95,7 @@ dependencies { implementation 'com.google.android.gms:play-services-ads:20.2.0' implementation 'com.google.android.gms:play-services-location:18.0.0' - implementation 'com.google.android.gms:play-services-maps:17.0.0' + implementation 'com.google.android.gms:play-services-maps:18.0.0' implementation 'com.google.firebase:firebase-core:20.0.0' implementation 'com.google.firebase:firebase-analytics:20.0.0' diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/cameras/CameraFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/cameras/CameraFragment.kt index dc92360d..c98aea95 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/cameras/CameraFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/cameras/CameraFragment.kt @@ -117,7 +117,7 @@ class CameraFragment : DaggerFragment(), Injectable, OnMapReadyCallback { t?.cancel() } - override fun onMapReady(map: GoogleMap?) { + override fun onMapReady(map: GoogleMap) { mMap = map as GoogleMap diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt index e5447373..45833c5a 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt @@ -124,7 +124,7 @@ class VesselWatchFragment: DaggerFragment(), Injectable, OnMapReadyCallback, Goo } - override fun onMapReady(map: GoogleMap?) { + override fun onMapReady(map: GoogleMap) { mMap = map as GoogleMap diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/highwayAlerts/HighwayAlertFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/highwayAlerts/HighwayAlertFragment.kt index 7970e05b..8da0e323 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/highwayAlerts/HighwayAlertFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/highwayAlerts/HighwayAlertFragment.kt @@ -95,7 +95,7 @@ class HighwayAlertFragment : DaggerFragment(), Injectable, OnMapReadyCallback { return dataBinding.root } - override fun onMapReady(map: GoogleMap?) { + override fun onMapReady(map: GoogleMap) { mMap = map as GoogleMap diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/tollrates/tollsigns/TollTripFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/tollrates/tollsigns/TollTripFragment.kt index d506e4c9..4e0a7c85 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/tollrates/tollsigns/TollTripFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/tollrates/tollsigns/TollTripFragment.kt @@ -49,7 +49,7 @@ class TollTripFragment : DaggerFragment(), Injectable, OnMapReadyCallback { return dataBinding.root } - override fun onMapReady(map: GoogleMap?) { + override fun onMapReady(map: GoogleMap) { mMap = map as GoogleMap mMap.uiSettings.isMapToolbarEnabled = false diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt index b225768c..58a9777a 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt @@ -255,7 +255,7 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, return false } - override fun onMapReady(map: GoogleMap?) { + override fun onMapReady(map: GoogleMap) { mMap = map as GoogleMap diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/restareas/RestAreaFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/restareas/RestAreaFragment.kt index 944e6d29..c7685c28 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/restareas/RestAreaFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/restareas/RestAreaFragment.kt @@ -74,7 +74,7 @@ class RestAreaFragment: DaggerFragment(), Injectable, OnMapReadyCallback { return dataBinding.root } - override fun onMapReady(map: GoogleMap?) { + override fun onMapReady(map: GoogleMap) { mMap = map as GoogleMap diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/travelerinformation/bridgeAlerts/BridgeAlertFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/travelerinformation/bridgeAlerts/BridgeAlertFragment.kt index a038b687..2140ad9a 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/travelerinformation/bridgeAlerts/BridgeAlertFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/travelerinformation/bridgeAlerts/BridgeAlertFragment.kt @@ -88,7 +88,7 @@ class BridgeAlertFragment : DaggerFragment(), Injectable, OnMapReadyCallback { return dataBinding.root } - override fun onMapReady(map: GoogleMap?) { + override fun onMapReady(map: GoogleMap) { mMap = map as GoogleMap From c9fc94d32abb5d89943ae2cc74a18f0806e5d377 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Mon, 6 Dec 2021 11:17:21 -0800 Subject: [PATCH 06/23] Update Activity Dependency Project would not compile after Activity Dependency update. Type mismatch error in onCreateOptionsMenu override function. Removed null value assignment from non-nullable variable to fix type mismatch. --- app/build.gradle | 2 +- app/src/main/java/gov/wa/wsdot/android/wsdot/ui/MainActivity.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 65cd4cc7..b96f6bc8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -88,7 +88,7 @@ dependencies { implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation 'com.google.android.material:material:1.4.0' - implementation 'androidx.activity:activity-ktx:1.1.0' + implementation 'androidx.activity:activity-ktx:1.4.0' implementation 'androidx.fragment:fragment-ktx:1.2.5' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0' diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/MainActivity.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/MainActivity.kt index 642b2481..ae9dcc89 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/MainActivity.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/MainActivity.kt @@ -206,7 +206,7 @@ class MainActivity : DaggerAppCompatActivity(), NavigationView.OnNavigationItemS } - override fun onCreateOptionsMenu(menu: Menu?): Boolean { + override fun onCreateOptionsMenu(menu: Menu): Boolean { addMenuBadgeIfNeeded() return super.onCreateOptionsMenu(menu) } From 50ade2c7d96d7eee002c880a9655eb165d312a99 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Mon, 6 Dec 2021 11:18:54 -0800 Subject: [PATCH 07/23] Update Android 12 (SDK 31) Dependencies --- app/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b96f6bc8..05767529 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -89,11 +89,11 @@ dependencies { implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.activity:activity-ktx:1.4.0' - implementation 'androidx.fragment:fragment-ktx:1.2.5' + implementation 'androidx.fragment:fragment-ktx:1.4.0' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0' - implementation 'com.google.android.gms:play-services-ads:20.2.0' + implementation 'com.google.android.gms:play-services-ads:20.5.0' implementation 'com.google.android.gms:play-services-location:18.0.0' implementation 'com.google.android.gms:play-services-maps:18.0.0' @@ -102,8 +102,8 @@ dependencies { implementation 'com.google.firebase:firebase-messaging:23.0.0' implementation 'com.google.firebase:firebase-crashlytics-ktx:18.2.5' - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'androidx.core:core-ktx:1.3.2' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.preference:preference-ktx:1.1.1' From d52b4f6ab1ab4b3e4ed6149b63a426494f15d685 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Thu, 13 Jan 2022 13:45:48 -0800 Subject: [PATCH 08/23] Fix crash on device rotation Issue caused by ad view banner returning a null value. Declaring variable as nullable fixes issue. Resolves issue #131 --- .../wa/wsdot/android/wsdot/ui/MainActivity.kt | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/MainActivity.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/MainActivity.kt index ae9dcc89..2aa41a0f 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/MainActivity.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/MainActivity.kt @@ -57,7 +57,7 @@ class MainActivity : DaggerAppCompatActivity(), NavigationView.OnNavigationItemS private lateinit var firebaseAnalytics: FirebaseAnalytics - private lateinit var adView: AdManagerAdView + private var adView: AdManagerAdView? = null private val adSize: AdSize get() { @@ -201,8 +201,8 @@ class MainActivity : DaggerAppCompatActivity(), NavigationView.OnNavigationItemS adView = AdManagerAdView(this) ad_banner_box.addView(adView) - adView.setAdSizes(adSize) - adView.adUnitId = ApiKeys.UNIT_ID + adView?.setAdSizes(adSize) + adView?.adUnitId = ApiKeys.UNIT_ID } @@ -592,9 +592,9 @@ class MainActivity : DaggerAppCompatActivity(), NavigationView.OnNavigationItemS * Initialize and display ads. * WARNING: don't call in onCreate */ - fun enableAds(targets: Map) { + fun enableAds(targets: Map) { - ad_banner_box.visibility = VISIBLE + ad_banner_box?.visibility = VISIBLE //val testDeviceIds = Arrays.asList("2531EB5FD75758B5E8BDD4669A870BF7") //val configuration = RequestConfiguration.Builder() @@ -604,15 +604,15 @@ class MainActivity : DaggerAppCompatActivity(), NavigationView.OnNavigationItemS //.build() //MobileAds.setRequestConfiguration(configuration) - adView.pause() - adView.adListener = null - adView.adListener = object : AdListener() { + adView?.pause() + adView?.adListener = null + adView?.adListener = object : AdListener() { override fun onAdLoaded() { super.onAdLoaded() // report ad ID to crashlytics - val info = adView.responseInfo + val info = adView?.responseInfo var adResponseId = "null" if (info != null){ Log.e("Ads", info.toString()) @@ -632,7 +632,7 @@ class MainActivity : DaggerAppCompatActivity(), NavigationView.OnNavigationItemS } // Start loading the ad in the background. - adView.loadAd(adRequest.build()) + adView?.loadAd(adRequest.build()) } @@ -641,8 +641,8 @@ class MainActivity : DaggerAppCompatActivity(), NavigationView.OnNavigationItemS * WARNING: don't call in onCreate */ fun disableAds() { - ad_banner_box.visibility = GONE - adView.pause() + ad_banner_box?.visibility = GONE + adView?.pause() } fun setScreenName(screenName: String) { From 8f8442df9bc9b1227ae025236aec32cd55548a79 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Thu, 13 Jan 2022 13:46:20 -0800 Subject: [PATCH 09/23] Fix notification display issue Notifications were not displaying for API 31. Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent. Resolves issue #129 --- .../android/wsdot/service/MyFirebaseMessagingService.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/service/MyFirebaseMessagingService.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/service/MyFirebaseMessagingService.kt index 54ee5509..afbded90 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/service/MyFirebaseMessagingService.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/service/MyFirebaseMessagingService.kt @@ -4,7 +4,9 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.os.Build import android.util.Log +import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import androidx.preference.PreferenceManager import com.google.firebase.messaging.FirebaseMessagingService @@ -71,6 +73,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() { } } + @RequiresApi(Build.VERSION_CODES.M) private fun getNotificationIntent(data: MutableMap): PendingIntent { val type = data["type"] @@ -120,7 +123,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() { } } return PendingIntent.getActivity(this, alertId?.toInt() ?: 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT) + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } /** From 7de9e565441bc9e77e6c0960bf9c75b2963b9643 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Thu, 13 Jan 2022 13:46:32 -0800 Subject: [PATCH 10/23] Update bottom sheet view layout Bottom sheet view was not displaying correctly. Issue was introduced after updating material and constraintlayout dependencies. Resolved issue by increasing view size for highway alerts & web cameras to match parent view. Resolves issue #130 --- app/src/main/res/layout/map_fragment.xml | 3 --- app/src/main/res/layout/vessel_watch.xml | 2 -- 2 files changed, 5 deletions(-) diff --git a/app/src/main/res/layout/map_fragment.xml b/app/src/main/res/layout/map_fragment.xml index 2f061bb7..95f6a305 100644 --- a/app/src/main/res/layout/map_fragment.xml +++ b/app/src/main/res/layout/map_fragment.xml @@ -60,8 +60,6 @@ android:id="@+id/camera_bottom_sheet" android:layout_width="match_parent" android:layout_height="match_parent" - android:maxWidth="@dimen/camera_sheet_width" - android:maxHeight="@dimen/camera_sheet_height" android:clickable="true" android:focusable="true" android:layout_gravity="start" @@ -83,7 +81,6 @@ android:id="@+id/highway_alert_bottom_sheet" android:layout_width="match_parent" android:layout_height="wrap_content" - android:maxWidth="500dp" android:clickable="true" android:focusable="true" android:layout_gravity="start" diff --git a/app/src/main/res/layout/vessel_watch.xml b/app/src/main/res/layout/vessel_watch.xml index 623ab026..f553236a 100644 --- a/app/src/main/res/layout/vessel_watch.xml +++ b/app/src/main/res/layout/vessel_watch.xml @@ -43,8 +43,6 @@ android:id="@+id/camera_bottom_sheet" android:layout_width="match_parent" android:layout_height="match_parent" - android:maxWidth="@dimen/camera_sheet_width" - android:maxHeight="@dimen/camera_sheet_height" android:clickable="true" android:focusable="true" android:layout_gravity="start" From 9810470573ee84018130c603959c659cc0790a90 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Fri, 14 Jan 2022 16:15:57 -0800 Subject: [PATCH 11/23] Access Coarse Location Apps targeting Android 12 or higher feature approximate location permissions. - Adds coarse & fine location check for traffic and ferry map. - Adds coarse & fine location check for ferry sailings and Amtrak stations. - Adds coarse & fine location check when map location icon is selected. - Removes extra permission rational dialogs. - Fixes API 23 location alert issue. Resolves issue #127 --- app/src/main/AndroidManifest.xml | 1 + .../amtrakcascades/AmtrakCascadesFragment.kt | 122 ++++++++++++++--- .../ui/ferries/route/FerriesRouteFragment.kt | 123 +++++++++++++++--- .../vesselwatch/VesselWatchFragment.kt | 117 ++++++++++++++--- .../wsdot/ui/trafficmap/TrafficMapFragment.kt | 112 ++++++++++++---- 5 files changed, 394 insertions(+), 81 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 74263ef8..fce794db 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/amtrakcascades/AmtrakCascadesFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/amtrakcascades/AmtrakCascadesFragment.kt index aaecce0e..b921a76f 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/amtrakcascades/AmtrakCascadesFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/amtrakcascades/AmtrakCascadesFragment.kt @@ -2,12 +2,19 @@ package gov.wa.wsdot.android.wsdot.ui.amtrakcascades import android.Manifest import android.annotation.SuppressLint +import android.app.Activity +import android.content.pm.PackageManager import android.location.Location +import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper import android.view.* +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.RequiresApi import androidx.appcompat.app.AlertDialog +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import androidx.databinding.DataBindingUtil import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider @@ -23,8 +30,6 @@ import gov.wa.wsdot.android.wsdot.ui.common.callback.TapCallback import gov.wa.wsdot.android.wsdot.ui.common.viewmodel.SharedDateViewModel import gov.wa.wsdot.android.wsdot.util.autoCleared import permissions.dispatcher.NeedsPermission -import permissions.dispatcher.OnShowRationale -import permissions.dispatcher.PermissionRequest import permissions.dispatcher.RuntimePermissions import java.util.* import javax.inject.Inject @@ -51,6 +56,29 @@ class AmtrakCascadesFragment : DaggerFragment(), Injectable { private lateinit var fusedLocationClient: FusedLocationProviderClient + @RequiresApi(Build.VERSION_CODES.N) + val locationPermissionRequest = registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions() + ) { permissions -> + when { + permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> { + myLocationFineWithPermissionCheck() + println("Precise location access granted.") + + } + permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> { + myLocationCoarseWithPermissionCheck() + println("Coarse location access granted.") + + } + else -> { + println("No location access granted.") + } + } + } + + + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) // analytics @@ -142,7 +170,7 @@ class AmtrakCascadesFragment : DaggerFragment(), Injectable { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setClosestStationWithPermissionCheck() + checkAppPermissions() } private fun initDatePicker(){ @@ -183,9 +211,10 @@ class AmtrakCascadesFragment : DaggerFragment(), Injectable { } + // Location Permissions @SuppressLint("MissingPermission") @NeedsPermission(Manifest.permission.ACCESS_FINE_LOCATION) - fun setClosestStation() { + fun myLocationFine() { context?.let { context -> fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) fusedLocationClient.lastLocation @@ -200,9 +229,75 @@ class AmtrakCascadesFragment : DaggerFragment(), Injectable { } } - @OnShowRationale(Manifest.permission.ACCESS_FINE_LOCATION) - fun showRationaleForLocation(request: PermissionRequest) { - showRationaleDialog(R.string.permission_station_location_rationale, request) + @SuppressLint("MissingPermission") + @NeedsPermission(Manifest.permission.ACCESS_COARSE_LOCATION) + fun myLocationCoarse() { + context?.let { context -> + fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) + fusedLocationClient.lastLocation + .addOnSuccessListener { location : Location? -> + location?.let { + amtrakCascadesViewModel.selectStationNearestTo(it) + } + if (location == null) { + requestLocationUpdate() + } + } + } + } + + private fun checkAppPermissions() { + + if (Build.VERSION.SDK_INT == 23) { + myLocationFineWithPermissionCheck() + } else { + + // Check if app has location permissions granted + when (PackageManager.PERMISSION_GRANTED) { + activity?.let { + ContextCompat.checkSelfPermission( + it, + Manifest.permission.ACCESS_FINE_LOCATION + ) + } + -> { + myLocationFineWithPermissionCheck() + } + activity?.let { + ContextCompat.checkSelfPermission( + it, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + } + -> { + myLocationCoarseWithPermissionCheck() + } + else -> { + + // show permission rational dialog + if (ActivityCompat.shouldShowRequestPermissionRationale( + context as Activity, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + ) { + AlertDialog.Builder(context!!) + .setTitle("Location Permission") + .setMessage(R.string.permission_station_location_rationale) + .setCancelable(false) + .setPositiveButton("next") + { _, _ -> + locationPermissionRequest.launch( + arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + ) + } + .show() + } + } + } + } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { @@ -211,18 +306,7 @@ class AmtrakCascadesFragment : DaggerFragment(), Injectable { onRequestPermissionsResult(requestCode, grantResults) } - private fun showRationaleDialog(rationMessage: Int, permRequest: PermissionRequest) { - context?.let { - val builder = AlertDialog.Builder(it) - builder.setTitle("Location Permission") - builder.setMessage(rationMessage) - builder.setCancelable(false) - builder.setPositiveButton("next") { _, _ -> permRequest.proceed()} - val dialog: AlertDialog = builder.create() - dialog.show() - } - } - + @SuppressLint("MissingPermission") private fun requestLocationUpdate() { val locationRequest = LocationRequest() diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/route/FerriesRouteFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/route/FerriesRouteFragment.kt index ebae39cd..47afca88 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/route/FerriesRouteFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/route/FerriesRouteFragment.kt @@ -2,13 +2,20 @@ package gov.wa.wsdot.android.wsdot.ui.ferries.route import android.Manifest import android.annotation.SuppressLint +import android.app.Activity +import android.content.pm.PackageManager import android.location.Location +import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper import android.util.Log import android.view.* +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.RequiresApi import androidx.appcompat.app.AlertDialog +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentStatePagerAdapter @@ -39,8 +46,6 @@ import gov.wa.wsdot.android.wsdot.util.AdTargets import gov.wa.wsdot.android.wsdot.util.autoCleared import gov.wa.wsdot.android.wsdot.util.putDouble import permissions.dispatcher.NeedsPermission -import permissions.dispatcher.OnShowRationale -import permissions.dispatcher.PermissionRequest import permissions.dispatcher.RuntimePermissions import java.util.* import java.util.Calendar.* @@ -81,6 +86,29 @@ class FerriesRouteFragment : DaggerFragment(), Injectable { private lateinit var fusedLocationClient: FusedLocationProviderClient + @RequiresApi(Build.VERSION_CODES.N) + val locationPermissionRequest = registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions() + ) { permissions -> + when { + permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> { + myLocationFineWithPermissionCheck() + println("Precise location access granted.") + + } + permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> { + myLocationCoarseWithPermissionCheck() + println("Coarse location access granted.") + + } + else -> { + println("No location access granted.") + } + } + } + + + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) (activity as MainActivity).setScreenName(this::class.java.simpleName) @@ -210,7 +238,7 @@ class FerriesRouteFragment : DaggerFragment(), Injectable { val tabLayout: TabLayout = view.findViewById(R.id.tab_layout) tabLayout.setupWithViewPager(viewPager) - setClosestTerminalWithPermissionCheck() + checkAppPermissions() } @@ -260,9 +288,10 @@ class FerriesRouteFragment : DaggerFragment(), Injectable { } + // Location Permissions @SuppressLint("MissingPermission") @NeedsPermission(Manifest.permission.ACCESS_FINE_LOCATION) - fun setClosestTerminal() { + fun myLocationFine() { context?.let { context -> fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) fusedLocationClient.lastLocation @@ -277,29 +306,85 @@ class FerriesRouteFragment : DaggerFragment(), Injectable { } } - @OnShowRationale(Manifest.permission.ACCESS_FINE_LOCATION) - fun showRationaleForLocation(request: PermissionRequest) { - showRationaleDialog(R.string.permission_terminal_location_rationale, request) + @SuppressLint("MissingPermission") + @NeedsPermission(Manifest.permission.ACCESS_COARSE_LOCATION) + fun myLocationCoarse() { + context?.let { context -> + fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) + fusedLocationClient.lastLocation + .addOnSuccessListener { location : Location? -> + location?.let { + routeViewModel.selectTerminalNearestTo(it) + } + if (location == null) { + requestLocationUpdate() + } + } + } + } + + private fun checkAppPermissions() { + + if (Build.VERSION.SDK_INT == 23) { + myLocationFineWithPermissionCheck() + } else { + + // Check if app has location permissions granted + when (PackageManager.PERMISSION_GRANTED) { + activity?.let { + ContextCompat.checkSelfPermission( + it, + Manifest.permission.ACCESS_FINE_LOCATION + ) + } + -> { + myLocationFineWithPermissionCheck() + } + activity?.let { + ContextCompat.checkSelfPermission( + it, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + } + -> { + myLocationCoarseWithPermissionCheck() + } + else -> { + + // show permission rational dialog + if (ActivityCompat.shouldShowRequestPermissionRationale( + context as Activity, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + ) { + AlertDialog.Builder(context!!) + .setTitle("Location Permission") + .setMessage(R.string.permission_terminal_location_rationale) + .setCancelable(false) + .setPositiveButton("next") + { _, _ -> + locationPermissionRequest.launch( + arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + ) + } + .show() + } + } + } + } } + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) // NOTE: delegate the permission handling to generated function onRequestPermissionsResult(requestCode, grantResults) } - private fun showRationaleDialog(rationMessage: Int, permRequest: PermissionRequest) { - context?.let { - val builder = AlertDialog.Builder(it) - builder.setTitle("Location Permission") - builder.setMessage(rationMessage) - builder.setCancelable(false) - builder.setPositiveButton("next") { _, _ -> permRequest.proceed()} - val dialog: AlertDialog = builder.create() - dialog.show() - } - } - + @SuppressLint("MissingPermission") private fun requestLocationUpdate() { val locationRequest = LocationRequest() diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt index 45833c5a..dd3b9550 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt @@ -2,8 +2,10 @@ package gov.wa.wsdot.android.wsdot.ui.ferries.vesselwatch import android.Manifest import android.annotation.SuppressLint +import android.content.pm.PackageManager import android.content.res.Resources import android.location.Location +import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper @@ -14,13 +16,14 @@ import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.activity.OnBackPressedCallback -import androidx.appcompat.app.AlertDialog +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat import androidx.databinding.DataBindingUtil import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController -import com.google.android.gms.location.FusedLocationProviderClient -import com.google.android.gms.location.LocationServices +import com.google.android.gms.location.* import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.OnMapReadyCallback @@ -42,8 +45,6 @@ import gov.wa.wsdot.android.wsdot.util.getDouble import gov.wa.wsdot.android.wsdot.model.common.Status import gov.wa.wsdot.android.wsdot.util.putDouble import permissions.dispatcher.NeedsPermission -import permissions.dispatcher.OnShowRationale -import permissions.dispatcher.PermissionRequest import permissions.dispatcher.RuntimePermissions import javax.inject.Inject @@ -77,6 +78,28 @@ class VesselWatchFragment: DaggerFragment(), Injectable, OnMapReadyCallback, Goo } } + @RequiresApi(Build.VERSION_CODES.N) + val locationPermissionRequest = registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions() + ) { permissions -> + when { + permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> { + myLocationFineWithPermissionCheck() + println("Precise location access granted.") + + } + permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> { + myLocationCoarseWithPermissionCheck() + println("Coarse location access granted.") + + } + else -> { + println("No location access granted.") + } + } + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) (activity as MainActivity).setScreenName(this::class.java.simpleName) @@ -146,7 +169,7 @@ class VesselWatchFragment: DaggerFragment(), Injectable, OnMapReadyCallback, Goo } } - enableMyLocationWithPermissionCheck() + checkAppPermissions() val settings = PreferenceManager.getDefaultSharedPreferences(activity) @@ -348,21 +371,71 @@ class VesselWatchFragment: DaggerFragment(), Injectable, OnMapReadyCallback, Goo // Location Permission @SuppressLint("MissingPermission") @NeedsPermission(Manifest.permission.ACCESS_FINE_LOCATION) - fun enableMyLocation() { + fun myLocationFine() { + context?.let { context -> + fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) + fusedLocationClient.lastLocation + .addOnSuccessListener { location: Location? -> + location?.let { + mMap?.isMyLocationEnabled = true + requestLocationUpdates() + } + } + } + } + + @SuppressLint("MissingPermission") + @NeedsPermission(Manifest.permission.ACCESS_COARSE_LOCATION) + fun myLocationCoarse() { context?.let { context -> fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) fusedLocationClient.lastLocation .addOnSuccessListener { location : Location? -> location?.let { mMap?.isMyLocationEnabled = true + requestLocationUpdates() } } } } - @OnShowRationale(Manifest.permission.ACCESS_FINE_LOCATION) - fun showRationaleForLocation(request: PermissionRequest) { - showRationaleDialog(R.string.permission_map_location_rationale, request) + private fun checkAppPermissions() { + + if (Build.VERSION.SDK_INT == 23) { + myLocationFineWithPermissionCheck() + } else { + + // Check if app has location permissions granted + when (PackageManager.PERMISSION_GRANTED) { + activity?.let { + ContextCompat.checkSelfPermission( + it, + Manifest.permission.ACCESS_FINE_LOCATION + ) + } + -> { + myLocationFineWithPermissionCheck() + } + activity?.let { + ContextCompat.checkSelfPermission( + it, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + } + -> { + myLocationCoarseWithPermissionCheck() + } + else -> { + // Present permission dialog to request permission type + locationPermissionRequest.launch( + arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + ) + } + } + } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { @@ -370,16 +443,20 @@ class VesselWatchFragment: DaggerFragment(), Injectable, OnMapReadyCallback, Goo onRequestPermissionsResult(requestCode, grantResults) } - private fun showRationaleDialog(rationMessage: Int, permRequest: PermissionRequest) { - context?.let { - val builder = AlertDialog.Builder(it) - builder.setTitle("Location Permission") - builder.setMessage(rationMessage) - builder.setCancelable(false) - builder.setPositiveButton("next") { _, _ -> permRequest.proceed()} - val dialog: AlertDialog = builder.create() - dialog.show() + @SuppressLint("MissingPermission") + private fun requestLocationUpdates() { + + val locationRequest = LocationRequest() + locationRequest.numUpdates = 1 + val locationCallback = object : LocationCallback() { + override fun onLocationResult(locationResult: LocationResult?) { + locationResult ?: return + } } - } + fusedLocationClient.requestLocationUpdates( + locationRequest, + locationCallback, + Looper.getMainLooper()) + } } \ No newline at end of file diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt index 58a9777a..e9fc4e51 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt @@ -2,10 +2,12 @@ package gov.wa.wsdot.android.wsdot.ui.trafficmap import android.Manifest import android.annotation.SuppressLint +import android.content.pm.PackageManager import android.content.res.ColorStateList import android.content.res.Resources import android.graphics.Color import android.location.Location +import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper @@ -17,8 +19,11 @@ import android.view.* import android.widget.EditText import android.widget.Toast import androidx.activity.OnBackPressedCallback +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.RequiresApi import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar +import androidx.core.content.ContextCompat import androidx.databinding.DataBindingUtil import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider @@ -57,10 +62,7 @@ import gov.wa.wsdot.android.wsdot.util.* import gov.wa.wsdot.android.wsdot.util.map.CameraClusterManager import gov.wa.wsdot.android.wsdot.util.map.CameraRenderer import permissions.dispatcher.NeedsPermission -import permissions.dispatcher.OnShowRationale -import permissions.dispatcher.PermissionRequest import permissions.dispatcher.RuntimePermissions -import java.lang.Boolean.getBoolean import java.util.* import javax.inject.Inject import kotlin.collections.HashMap @@ -127,6 +129,27 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, } } + // Determine which permissions have been granted + @RequiresApi(Build.VERSION_CODES.N) + val locationPermissionRequest = registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions() + ) { permissions -> + when { + permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> { + myLocationFineWithPermissionCheck() + println("Precise location access granted.") + + } + permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> { + myLocationCoarseWithPermissionCheck() + println("Coarse location access granted.") + + } else -> { + println("No location access granted.") + } + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) @@ -245,10 +268,26 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, super.onCreateOptionsMenu(menu, inflater) } + // Check location permissions when menu item is selected override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_my_location -> { - goToMyLocationWithPermissionCheck() + if (activity?.let { + ContextCompat.checkSelfPermission( + it, + Manifest.permission.ACCESS_FINE_LOCATION) + } + == PackageManager.PERMISSION_GRANTED) { + myLocationFineWithPermissionCheck() + } + else if (activity?.let { + ContextCompat.checkSelfPermission( + it, + Manifest.permission.ACCESS_COARSE_LOCATION) + } + == PackageManager.PERMISSION_GRANTED) { + myLocationCoarseWithPermissionCheck() + } } else -> {} } @@ -279,7 +318,7 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, mMap.clear() - enableMyLocationWithPermissionCheck() + checkAppPermissions() mMap.uiSettings.isCompassEnabled = true mMap.uiSettings.isMyLocationButtonEnabled = false @@ -987,33 +1026,70 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, @SuppressLint("MissingPermission") @NeedsPermission(Manifest.permission.ACCESS_FINE_LOCATION) - fun enableMyLocation() { + fun myLocationFine() { context?.let { context -> fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) fusedLocationClient.lastLocation .addOnSuccessListener { location : Location? -> mMap.isMyLocationEnabled = true requestLocationUpdates() + requestGoToLocationUpdate() } } } @SuppressLint("MissingPermission") - @NeedsPermission(Manifest.permission.ACCESS_FINE_LOCATION) - fun goToMyLocation() { + @NeedsPermission(Manifest.permission.ACCESS_COARSE_LOCATION) + fun myLocationCoarse() { context?.let { context -> fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) fusedLocationClient.lastLocation .addOnSuccessListener { location : Location? -> mMap.isMyLocationEnabled = true + requestLocationUpdates() requestGoToLocationUpdate() } } } - @OnShowRationale(Manifest.permission.ACCESS_FINE_LOCATION) - fun showRationaleForLocation(request: PermissionRequest) { - showRationaleDialog(R.string.permission_map_location_rationale, request) + private fun checkAppPermissions() { + + // API 23 requires fine location alert dialog + if (Build.VERSION.SDK_INT == 23) { + myLocationFineWithPermissionCheck() + } else { + + // Check if app has location permissions granted + when (PackageManager.PERMISSION_GRANTED) { + activity?.let { + ContextCompat.checkSelfPermission( + it, + Manifest.permission.ACCESS_FINE_LOCATION + ) + } + -> { + myLocationFineWithPermissionCheck() + } + activity?.let { + ContextCompat.checkSelfPermission( + it, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + } + -> { + myLocationCoarseWithPermissionCheck() + } + else -> { + // Present permission dialog to request permission type + locationPermissionRequest.launch( + arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + ) + } + } + } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { @@ -1021,18 +1097,7 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, onRequestPermissionsResult(requestCode, grantResults) } - private fun showRationaleDialog(rationMessage: Int, permRequest: PermissionRequest) { - context?.let { - val builder = AlertDialog.Builder(it) - builder.setTitle("Location Permission") - builder.setMessage(rationMessage) - builder.setCancelable(false) - builder.setPositiveButton("next") { _, _ -> permRequest.proceed()} - val dialog: AlertDialog = builder.create() - dialog.show() - } - } - + @SuppressLint("MissingPermission") private fun requestGoToLocationUpdate() { val locationRequest = LocationRequest() @@ -1055,6 +1120,7 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(LatLng(location.latitude, location.longitude), 15.0f)) } + @SuppressLint("MissingPermission") private fun requestLocationUpdates() { val locationRequest = LocationRequest() From 9763838ae80169a8df86b8577245e82cbff879d0 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Tue, 18 Jan 2022 08:17:41 -0800 Subject: [PATCH 12/23] Enable ferry alert view hyperlinks Works for API 24 and above Resolves issue #128 --- .../android/wsdot/ui/common/binding/BindingAdapters.kt | 9 ++++++++- app/src/main/res/layout/ferry_alert_item.xml | 1 - 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/common/binding/BindingAdapters.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/common/binding/BindingAdapters.kt index 1ae03ab0..dae45f5d 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/common/binding/BindingAdapters.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/common/binding/BindingAdapters.kt @@ -19,7 +19,9 @@ package gov.wa.wsdot.android.wsdot.ui.common.binding import android.annotation.SuppressLint import android.content.res.ColorStateList import android.graphics.Color +import android.os.Build import android.text.Html +import android.text.method.LinkMovementMethod import android.view.View import android.widget.* import androidx.databinding.BindingAdapter @@ -213,7 +215,12 @@ object BindingAdapters { if (text != null) { if (text != "") { - textView.text = stripHtml(text).trimEnd() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + textView.text = Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY).trimEnd() + textView.movementMethod = LinkMovementMethod.getInstance() + } else { + textView.text = stripHtml(text).trimEnd() + } } } } diff --git a/app/src/main/res/layout/ferry_alert_item.xml b/app/src/main/res/layout/ferry_alert_item.xml index fcfedbab..44189d9d 100644 --- a/app/src/main/res/layout/ferry_alert_item.xml +++ b/app/src/main/res/layout/ferry_alert_item.xml @@ -49,7 +49,6 @@ app:layout_constraintTop_toBottomOf="@id/titleView" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - android:autoLink="all" android:paddingStart="8dp" android:paddingEnd="8dp" android:layout_marginTop="16dp" From 4f9ba666b754742500497d2bbcb4513cb11c324d Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Tue, 18 Jan 2022 08:19:36 -0800 Subject: [PATCH 13/23] Decrease ferry data cache time Resolves issue #132 --- .../wa/wsdot/android/wsdot/repository/FerriesRepository.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/repository/FerriesRepository.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/repository/FerriesRepository.kt index fb9afd4f..ef69e5f2 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/repository/FerriesRepository.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/repository/FerriesRepository.kt @@ -61,7 +61,7 @@ class FerriesRepository @Inject constructor( if (data != null && data.isNotEmpty()) { - if (TimeUtils.isOverXMinOld(data[0].localCacheDate, x = 15)) { + if (TimeUtils.isOverXMinOld(data[0].localCacheDate, x = 5)) { update = true } } else { @@ -94,7 +94,7 @@ class FerriesRepository @Inject constructor( if (data != null) { - if (TimeUtils.isOverXMinOld(data.localCacheDate, x = 15)) { + if (TimeUtils.isOverXMinOld(data.localCacheDate, x = 5)) { update = true } } else { @@ -267,7 +267,7 @@ class FerriesRepository @Inject constructor( if (data != null && data.isNotEmpty()) { - if (TimeUtils.isOverXMinOld(data[0].localCacheDate, x = 15)) { + if (TimeUtils.isOverXMinOld(data[0].localCacheDate, x = 5)) { update = true } } else { From 5004a0a90b8471c872675e20124c16fdff9eecef Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Tue, 18 Jan 2022 08:33:00 -0800 Subject: [PATCH 14/23] Remove Banner Ads Ads are not displaying correctly. Disabled ads and removed blank banner box from view. Resolves issue #133 --- .../amtrakcascades/AmtrakCascadesFragment.kt | 6 ++-- .../BaseCrossingTimesFragment.kt | 6 ++-- .../wsdot/ui/favorites/FavoritesFragment.kt | 33 ++++++++++++------- .../wsdot/ui/ferries/FerriesHomeFragment.kt | 12 ++++--- .../ui/ferries/route/FerriesRouteFragment.kt | 12 ++++--- .../MountainPassHomeFragment.kt | 6 ++-- .../wsdot/ui/trafficmap/TrafficMapFragment.kt | 6 ++-- app/src/main/res/layout/activity_main.xml | 1 + 8 files changed, 53 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/amtrakcascades/AmtrakCascadesFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/amtrakcascades/AmtrakCascadesFragment.kt index b921a76f..e72a82df 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/amtrakcascades/AmtrakCascadesFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/amtrakcascades/AmtrakCascadesFragment.kt @@ -95,8 +95,10 @@ class AmtrakCascadesFragment : DaggerFragment(), Injectable { savedInstanceState: Bundle? ): View? { - val adTargets = mapOf("wsdotapp" to "other") - (activity as MainActivity).enableAds(adTargets) +// val adTargets = mapOf("wsdotapp" to "other") +// (activity as MainActivity).enableAds(adTargets) + + (activity as MainActivity).disableAds() // set up view models amtrakCascadesViewModel = activity?.run { diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/bordercrossings/crossingtimes/BaseCrossingTimesFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/bordercrossings/crossingtimes/BaseCrossingTimesFragment.kt index 3cc0248c..eb8f7e3c 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/bordercrossings/crossingtimes/BaseCrossingTimesFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/bordercrossings/crossingtimes/BaseCrossingTimesFragment.kt @@ -46,8 +46,10 @@ abstract class BaseCrossingTimesFragment : DaggerFragment(), Injectable { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val adTargets = mapOf("wsdotapp" to resources.getString(R.string.ad_target_border)) - (activity as MainActivity).enableAds(adTargets) +// val adTargets = mapOf("wsdotapp" to resources.getString(R.string.ad_target_border)) +// (activity as MainActivity).enableAds(adTargets) + + (activity as MainActivity).disableAds() borderCrossingViewModel = ViewModelProvider(this, viewModelFactory) .get(BorderCrossingViewModel::class.java) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/favorites/FavoritesFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/favorites/FavoritesFragment.kt index 38f36373..0242a7e5 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/favorites/FavoritesFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/favorites/FavoritesFragment.kt @@ -468,25 +468,34 @@ class FavoritesFragment : DaggerFragment(), AdapterDataSetChangedListener, Injec private fun navigateToCamera(camera: Camera){ val action = NavGraphDirections.actionGlobalNavCameraFragment(camera.cameraId, camera.title) - val adTargets = mapOf("wsdotapp" to resources.getString(R.string.ad_target_traffic)) - (activity as MainActivity).enableAds(adTargets) +// val adTargets = mapOf("wsdotapp" to resources.getString(R.string.ad_target_traffic)) +// (activity as MainActivity).enableAds(adTargets) + + (activity as MainActivity).disableAds() + findNavController().navigate(action) } private fun navigateToSchedule(schedule: FerrySchedule) { val action = NavGraphDirections.actionGlobalNavFerriesRouteFragment(schedule.routeId, schedule.description) - val adTargets = mapOf( - "wsdotapp" to resources.getString(R.string.ad_target_ferries), - "wsdotferries" to AdTargets.getFerryAdTarget(schedule.routeId) - ) - (activity as MainActivity).enableAds(adTargets) +// val adTargets = mapOf( +// "wsdotapp" to resources.getString(R.string.ad_target_ferries), +// "wsdotferries" to AdTargets.getFerryAdTarget(schedule.routeId) +// ) +// (activity as MainActivity).enableAds(adTargets) + + (activity as MainActivity).disableAds() + findNavController().navigate(action) } private fun navigateToMountainPass(pass: MountainPass) { val action = NavGraphDirections.actionGlobalNavMountainPassReportFragment(pass.passId, pass.passName) - val adTargets = mapOf("wsdotapp" to resources.getString(R.string.ad_target_passes)) - (activity as MainActivity).enableAds(adTargets) +// val adTargets = mapOf("wsdotapp" to resources.getString(R.string.ad_target_passes)) +// (activity as MainActivity).enableAds(adTargets) + + (activity as MainActivity).disableAds() + findNavController().navigate(action) } @@ -541,8 +550,10 @@ class FavoritesFragment : DaggerFragment(), AdapterDataSetChangedListener, Injec val action = FavoritesFragmentDirections.actionNavFavoritesFragmentToNavFavoriteTrafficMapFragment() - val adTargets = mapOf("wsdotapp" to resources.getString(R.string.ad_target_traffic)) - (activity as MainActivity).enableAds(adTargets) +// val adTargets = mapOf("wsdotapp" to resources.getString(R.string.ad_target_traffic)) +// (activity as MainActivity).enableAds(adTargets) + + (activity as MainActivity).disableAds() findNavController().navigate(action) } diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/FerriesHomeFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/FerriesHomeFragment.kt index 3cae67df..c391bb32 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/FerriesHomeFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/FerriesHomeFragment.kt @@ -62,11 +62,13 @@ class FerriesHomeFragment : DaggerFragment(), Injectable { savedInstanceState: Bundle? ): View? { - val adTargets = mapOf( - "wsdotapp" to resources.getString(R.string.ad_target_ferries), - "wsdotferries" to AdTargets.getFerryAdTarget(null) - ) - (activity as MainActivity).enableAds(adTargets) +// val adTargets = mapOf( +// "wsdotapp" to resources.getString(R.string.ad_target_ferries), +// "wsdotferries" to AdTargets.getFerryAdTarget(null) +// ) +// (activity as MainActivity).enableAds(adTargets) + + (activity as MainActivity).disableAds() ferriesViewModel = ViewModelProvider(this, viewModelFactory) .get(FerriesViewModel::class.java) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/route/FerriesRouteFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/route/FerriesRouteFragment.kt index 47afca88..42600345 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/route/FerriesRouteFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/route/FerriesRouteFragment.kt @@ -131,11 +131,13 @@ class FerriesRouteFragment : DaggerFragment(), Injectable { savedInstanceState: Bundle? ): View? { - val adTargets = mapOf( - "wsdotapp" to resources.getString(R.string.ad_target_ferries), - "wsdotferries" to AdTargets.getFerryAdTarget(args.routeId) - ) - (activity as MainActivity).enableAds(adTargets) +// val adTargets = mapOf( +// "wsdotapp" to resources.getString(R.string.ad_target_ferries), +// "wsdotferries" to AdTargets.getFerryAdTarget(args.routeId) +// ) +// (activity as MainActivity).enableAds(adTargets) + + (activity as MainActivity).disableAds() // set up view models routeViewModel = ViewModelProvider(this, viewModelFactory) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/mountainpasses/MountainPassHomeFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/mountainpasses/MountainPassHomeFragment.kt index 5b1bfc9f..b3603424 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/mountainpasses/MountainPassHomeFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/mountainpasses/MountainPassHomeFragment.kt @@ -54,8 +54,10 @@ class MountainPassHomeFragment : DaggerFragment(), Injectable { savedInstanceState: Bundle? ): View? { - val adTargets = mapOf("wsdotapp" to resources.getString(R.string.ad_target_passes)) - (activity as MainActivity).enableAds(adTargets) +// val adTargets = mapOf("wsdotapp" to resources.getString(R.string.ad_target_passes)) +// (activity as MainActivity).enableAds(adTargets) + + (activity as MainActivity).disableAds() passViewModel = ViewModelProvider(this, viewModelFactory) .get(MountainPassViewModel::class.java) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt index e9fc4e51..64071e32 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt @@ -160,8 +160,10 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val adTargets = mapOf("wsdotapp" to resources.getString(R.string.ad_target_traffic)) - (activity as MainActivity).enableAds(adTargets) +// val adTargets = mapOf("wsdotapp" to resources.getString(R.string.ad_target_traffic)) +// (activity as MainActivity).enableAds(adTargets) + + (activity as MainActivity).disableAds() (activity as MainActivity).setScreenName(this::class.java.simpleName) // Inflate the layout for this fragment diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index d8aec453..a8c30be3 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -50,6 +50,7 @@ android:paddingTop="8dp" android:gravity="center_horizontal" android:orientation="horizontal" + android:visibility="gone" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintBottom_toBottomOf="parent"> From d02185dabf979ac2f501abe3c3db5984610438ea Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Tue, 18 Jan 2022 08:36:45 -0800 Subject: [PATCH 15/23] Remove HOV Button Closes #126 --- .../android/wsdot/ui/about/AboutFragment.kt | 5 - app/src/main/res/layout/about_fragment.xml | 157 ++++++++---------- 2 files changed, 73 insertions(+), 89 deletions(-) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/about/AboutFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/about/AboutFragment.kt index 77d13da8..3e966be7 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/about/AboutFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/about/AboutFragment.kt @@ -52,11 +52,6 @@ class AboutFragment: DaggerFragment(), Injectable { startActivity(browserIntent) } - binding.heroButton.setOnClickListener { - val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.wsdot.wa.gov/travel/highways-bridges/hov/report-violator")) - startActivity(browserIntent) - } - binding.appBugReportButton.setOnClickListener { val emailIntent = Intent(Intent.ACTION_SEND) emailIntent.putExtra(Intent.EXTRA_EMAIL, arrayOf("webfeedback@wsdot.wa.gov")) diff --git a/app/src/main/res/layout/about_fragment.xml b/app/src/main/res/layout/about_fragment.xml index a222df9f..f35cd523 100644 --- a/app/src/main/res/layout/about_fragment.xml +++ b/app/src/main/res/layout/about_fragment.xml @@ -1,5 +1,6 @@ - - - - + android:layout_width="match_parent" + android:background="?android:attr/selectableItemBackground"> - + - + - + - + - + - + - + \ No newline at end of file From bfa34498bbdfd573e6100d499706c3fdd21fa752 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Tue, 18 Jan 2022 11:24:29 -0800 Subject: [PATCH 16/23] Update Dependencies --- app/build.gradle | 18 +++++++++--------- build.gradle | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 05767529..9cbd91f3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -90,17 +90,17 @@ dependencies { implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.activity:activity-ktx:1.4.0' implementation 'androidx.fragment:fragment-ktx:1.4.0' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0' implementation 'com.google.android.gms:play-services-ads:20.5.0' implementation 'com.google.android.gms:play-services-location:18.0.0' - implementation 'com.google.android.gms:play-services-maps:18.0.0' + implementation 'com.google.android.gms:play-services-maps:18.0.2' - implementation 'com.google.firebase:firebase-core:20.0.0' - implementation 'com.google.firebase:firebase-analytics:20.0.0' + implementation 'com.google.firebase:firebase-core:20.0.2' + implementation 'com.google.firebase:firebase-analytics:20.0.2' implementation 'com.google.firebase:firebase-messaging:23.0.0' - implementation 'com.google.firebase:firebase-crashlytics-ktx:18.2.5' + implementation 'com.google.firebase:firebase-crashlytics-ktx:18.2.6' implementation 'androidx.appcompat:appcompat:1.4.0' implementation 'androidx.core:core-ktx:1.7.0' @@ -115,9 +115,9 @@ dependencies { kapt "androidx.lifecycle:lifecycle-common-java8:2.4.0" implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation "androidx.room:room-runtime:2.3.0" + implementation "androidx.room:room-runtime:2.4.0" kapt "androidx.room:room-compiler:2.3.0" - annotationProcessor 'androidx.room:room-compiler:2.3.0' + annotationProcessor 'androidx.room:room-compiler:2.4.0' // drag and drop list implementation 'com.ernestoyaquello.dragdropswiperecyclerview:drag-drop-swipe-recyclerview:0.5.1' @@ -132,7 +132,7 @@ dependencies { kapt "com.android.databinding:compiler:3.1.4" // Navigation - https://developer.android.com/jetpack/androidx/releases/navigation - def nav_version = "2.3.3" + def nav_version = "2.3.5" implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" @@ -169,7 +169,7 @@ dependencies { // Optional -- Robolectric environment testImplementation 'androidx.test:core:1.4.0' androidTestImplementation 'androidx.test:core:1.4.0' - testImplementation "androidx.room:room-testing:2.3.0" + testImplementation "androidx.room:room-testing:2.4.0" androidTestImplementation "androidx.arch.core:core-testing:2.1.0" androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test.ext:junit:1.1.3' diff --git a/build.gradle b/build.gradle index 1e9aa80f..e648e557 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.6.0' + ext.kotlin_version = '1.6.10' repositories { google() jcenter() From b3a2bb80c9968782cf53d7869fe60b0abdc0c939 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Tue, 18 Jan 2022 11:27:57 -0800 Subject: [PATCH 17/23] Update Location Dependency Project would not compile after Location Dependency update. Type mismatch error in onLocationResult override function. Removed null value assignment from non-nullable variable to fix type mismatch. --- app/build.gradle | 2 +- .../android/wsdot/ui/amtrakcascades/AmtrakCascadesFragment.kt | 2 +- .../android/wsdot/ui/ferries/route/FerriesRouteFragment.kt | 2 +- .../wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt | 2 +- .../wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9cbd91f3..0ca020ff 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -94,7 +94,7 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0' implementation 'com.google.android.gms:play-services-ads:20.5.0' - implementation 'com.google.android.gms:play-services-location:18.0.0' + implementation 'com.google.android.gms:play-services-location:19.0.1' implementation 'com.google.android.gms:play-services-maps:18.0.2' implementation 'com.google.firebase:firebase-core:20.0.2' diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/amtrakcascades/AmtrakCascadesFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/amtrakcascades/AmtrakCascadesFragment.kt index e72a82df..becca378 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/amtrakcascades/AmtrakCascadesFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/amtrakcascades/AmtrakCascadesFragment.kt @@ -315,7 +315,7 @@ class AmtrakCascadesFragment : DaggerFragment(), Injectable { locationRequest.numUpdates = 1 val locationCallback = object : LocationCallback() { - override fun onLocationResult(locationResult: LocationResult?) { + override fun onLocationResult(locationResult: LocationResult) { locationResult ?: return locationResult.locations.first()?.let { amtrakCascadesViewModel.selectStationNearestTo(it) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/route/FerriesRouteFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/route/FerriesRouteFragment.kt index 42600345..84b05272 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/route/FerriesRouteFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/route/FerriesRouteFragment.kt @@ -393,7 +393,7 @@ class FerriesRouteFragment : DaggerFragment(), Injectable { locationRequest.numUpdates = 1 val locationCallback = object : LocationCallback() { - override fun onLocationResult(locationResult: LocationResult?) { + override fun onLocationResult(locationResult: LocationResult) { locationResult ?: return locationResult.locations.first()?.let { routeViewModel.selectTerminalNearestTo(it) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt index dd3b9550..68e8c96b 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt @@ -449,7 +449,7 @@ class VesselWatchFragment: DaggerFragment(), Injectable, OnMapReadyCallback, Goo val locationRequest = LocationRequest() locationRequest.numUpdates = 1 val locationCallback = object : LocationCallback() { - override fun onLocationResult(locationResult: LocationResult?) { + override fun onLocationResult(locationResult: LocationResult) { locationResult ?: return } } diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt index 64071e32..0bcbfaf1 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt @@ -1106,7 +1106,7 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, locationRequest.numUpdates = 1 val locationCallback = object : LocationCallback() { - override fun onLocationResult(locationResult: LocationResult?) { + override fun onLocationResult(locationResult: LocationResult) { locationResult ?: return goToUsersLocation(locationResult.lastLocation) } @@ -1128,7 +1128,7 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, val locationRequest = LocationRequest() locationRequest.numUpdates = 1 val locationCallback = object : LocationCallback() { - override fun onLocationResult(locationResult: LocationResult?) { + override fun onLocationResult(locationResult: LocationResult) { locationResult ?: return checkSpeed(locationResult.lastLocation) } From 05dee02310a3e73cee0acd531047142171a86b6c Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Thu, 20 Jan 2022 16:39:38 -0800 Subject: [PATCH 18/23] Disable approximate location map icon User location should not be displayed on map when approximate location is enabled. Issue #127 --- .../android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt | 2 +- .../wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt index 68e8c96b..3dcc6e88 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/ferries/vesselwatch/VesselWatchFragment.kt @@ -392,7 +392,7 @@ class VesselWatchFragment: DaggerFragment(), Injectable, OnMapReadyCallback, Goo fusedLocationClient.lastLocation .addOnSuccessListener { location : Location? -> location?.let { - mMap?.isMyLocationEnabled = true + mMap?.isMyLocationEnabled = false requestLocationUpdates() } } diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt index 0bcbfaf1..824a7ac5 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt @@ -1047,7 +1047,7 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) fusedLocationClient.lastLocation .addOnSuccessListener { location : Location? -> - mMap.isMyLocationEnabled = true + mMap.isMyLocationEnabled = false requestLocationUpdates() requestGoToLocationUpdate() } From 87ef2516d31c287eafb3dd977103692f4696a8d3 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Thu, 20 Jan 2022 16:40:03 -0800 Subject: [PATCH 19/23] Update map location icon logic - API 31: Adds ability to change to precise location after approximate location has been enabled. - API 23-30: Displays location alert dialog if location permission has not been selected. Issue #127 --- .../android/wsdot/ui/trafficmap/TrafficMapFragment.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt index 824a7ac5..ae60e31c 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt @@ -274,6 +274,7 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_my_location -> { + myLocationFineWithPermissionCheck() if (activity?.let { ContextCompat.checkSelfPermission( it, @@ -291,7 +292,14 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, myLocationCoarseWithPermissionCheck() } } - else -> {} + else -> { + locationPermissionRequest.launch( + arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + ) + } } return false } From de21a7176e90c4e9a41cf93b5e071f16e6419783 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Thu, 20 Jan 2022 16:40:14 -0800 Subject: [PATCH 20/23] Add comments to dependencies --- app/build.gradle | 6 ++++++ build.gradle | 2 ++ 2 files changed, 8 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 0ca020ff..41869f96 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -91,6 +91,8 @@ dependencies { implementation 'androidx.activity:activity-ktx:1.4.0' implementation 'androidx.fragment:fragment-ktx:1.4.0' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0' + + // Updating to 2.4.0 causes error. Duplicate classes found in modules. implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0' implementation 'com.google.android.gms:play-services-ads:20.5.0' @@ -116,6 +118,8 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation "androidx.room:room-runtime:2.4.0" + + // Updating to 2.4.1 causes app:kaptDebugKotlin error. kapt "androidx.room:room-compiler:2.3.0" annotationProcessor 'androidx.room:room-compiler:2.4.0' @@ -153,6 +157,8 @@ dependencies { kapt "com.google.dagger:dagger-compiler:$daggerVersion" kapt "com.google.dagger:dagger-android-processor:$daggerVersion" + + // Updating to 2.28.3 causes app:kaptDebugKotlin error. implementation "com.google.dagger:dagger:$daggerVersion" implementation "com.google.dagger:dagger-android-support:$daggerVersion" diff --git a/build.gradle b/build.gradle index e648e557..103e7d29 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,8 @@ buildscript { classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5" classpath 'com.google.gms:google-services:4.3.10' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1' + + // Updating to 2.0.0 causes error. Manifest merger failed. classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:1.2.0" } From 70213980e45127d063c00436c4507f5e4e25e01a Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Mon, 31 Jan 2022 08:38:00 -0800 Subject: [PATCH 21/23] Clean up options menu code Replaces permission check code with checkAppPermissions function. --- .../wsdot/ui/trafficmap/TrafficMapFragment.kt | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt index ae60e31c..9f7395d8 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt @@ -275,35 +275,15 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, when (item.itemId) { R.id.action_my_location -> { myLocationFineWithPermissionCheck() - if (activity?.let { - ContextCompat.checkSelfPermission( - it, - Manifest.permission.ACCESS_FINE_LOCATION) - } - == PackageManager.PERMISSION_GRANTED) { - myLocationFineWithPermissionCheck() - } - else if (activity?.let { - ContextCompat.checkSelfPermission( - it, - Manifest.permission.ACCESS_COARSE_LOCATION) - } - == PackageManager.PERMISSION_GRANTED) { - myLocationCoarseWithPermissionCheck() - } - } - else -> { - locationPermissionRequest.launch( - arrayOf( - Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.ACCESS_COARSE_LOCATION - ) - ) + checkAppPermissions() } } + return false + } + override fun onMapReady(map: GoogleMap) { mMap = map as GoogleMap From f75915477163702c8c96c86f3e285e5f883d4c3b Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Mon, 31 Jan 2022 10:36:45 -0800 Subject: [PATCH 22/23] Add boolean check to location permissions Limits the number of times user can upgrade from approximate to precise location. Issue #127 --- .../android/wsdot/ui/trafficmap/TrafficMapFragment.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt index 9f7395d8..c05e1e59 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt @@ -101,6 +101,7 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, var showAlerts: Boolean = true var showRestAreas: Boolean = true + var requestLocationUpgrade: Boolean = true private lateinit var mMap: GoogleMap @@ -274,7 +275,9 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_my_location -> { - myLocationFineWithPermissionCheck() + if (requestLocationUpgrade) { + myLocationFineWithPermissionCheck() + } checkAppPermissions() } } @@ -1068,6 +1071,11 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, } -> { myLocationCoarseWithPermissionCheck() + + if (Build.VERSION.SDK_INT > 30) { + requestLocationUpgrade = false + } + } else -> { // Present permission dialog to request permission type From 2f0d5df6e2208213a0fec1d8e5efc7bfa66a9ce9 Mon Sep 17 00:00:00 2001 From: Andrew Englehorn Date: Mon, 31 Jan 2022 10:39:35 -0800 Subject: [PATCH 23/23] Add approximate location radius circle Radius circle shows device's location to within about 1 mile. Issue #127 --- .../wsdot/ui/trafficmap/TrafficMapFragment.kt | 54 +++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt index c05e1e59..7cb25e39 100644 --- a/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt +++ b/app/src/main/java/gov/wa/wsdot/android/wsdot/ui/trafficmap/TrafficMapFragment.kt @@ -29,11 +29,13 @@ import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController import com.google.android.gms.location.* +import com.google.android.gms.location.LocationRequest.PRIORITY_HIGH_ACCURACY import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.OnMapReadyCallback import com.google.android.gms.maps.SupportMapFragment import com.google.android.gms.maps.model.* +import com.google.android.gms.tasks.CancellationTokenSource import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior.* @@ -118,6 +120,12 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, // Camera update task timer var t: Timer? = null + // Approximate location radius circle + private var radiusCircle: Circle? = null + + // Current location cancellation token + private var cancellationTokenSource = CancellationTokenSource() + // FAB private lateinit var mFab: SpeedDialView @@ -1020,6 +1028,7 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, @SuppressLint("MissingPermission") @NeedsPermission(Manifest.permission.ACCESS_FINE_LOCATION) fun myLocationFine() { + radiusCircle?.remove() context?.let { context -> fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) fusedLocationClient.lastLocation @@ -1036,15 +1045,27 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, fun myLocationCoarse() { context?.let { context -> fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) - fusedLocationClient.lastLocation - .addOnSuccessListener { location : Location? -> - mMap.isMyLocationEnabled = false - requestLocationUpdates() - requestGoToLocationUpdate() + fusedLocationClient.getCurrentLocation(PRIORITY_HIGH_ACCURACY, cancellationTokenSource.token).addOnSuccessListener { location : Location? -> + mMap.isMyLocationEnabled = false + if(location != null) { + circle(location) } + } + requestCoarseLocationUpdate() } } + private fun circle(location: Location) { + val circleOptions = CircleOptions() + .center(LatLng(location.latitude, location.longitude)) + .radius(location.accuracy.toDouble()) + .strokeWidth(5F) + .strokeColor(0x3571cce7) + .fillColor(0x3571cce7) + radiusCircle?.remove() + radiusCircle = mMap.addCircle(circleOptions) + } + private fun checkAppPermissions() { // API 23 requires fine location alert dialog @@ -1095,6 +1116,29 @@ class TrafficMapFragment : DaggerFragment(), Injectable, OnMapReadyCallback, onRequestPermissionsResult(requestCode, grantResults) } + @SuppressLint("MissingPermission") + private fun requestCoarseLocationUpdate() { + + val locationRequest = LocationRequest() + locationRequest.numUpdates = 1 + + // Request location update every 60 seconds + locationRequest.interval = 60000 + + val locationCallback = object : LocationCallback() { + override fun onLocationResult(locationResult: LocationResult) { + locationResult ?: return + goToUsersLocation(locationResult.lastLocation) + } + } + + fusedLocationClient.requestLocationUpdates( + locationRequest, + locationCallback, + Looper.getMainLooper()) + } + + @SuppressLint("MissingPermission") private fun requestGoToLocationUpdate() {