diff --git a/app/build.gradle b/app/build.gradle index c646175d..4328b96d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "org.blitzortung.android.app" minSdkVersion 21 targetSdkVersion 34 - versionCode 321 - versionName '2.2.4' + versionCode 322 + versionName '2.2.5' multiDexEnabled false testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -59,15 +59,15 @@ android { } -def dagger_version = '2.51.1' +def dagger_version = '2.52' dependencies { implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.media:media:1.7.0' implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.recyclerview:recyclerview:1.3.2' - implementation 'androidx.work:work-runtime-ktx:2.9.0' - implementation 'org.osmdroid:osmdroid-android:6.1.18' + implementation 'androidx.work:work-runtime-ktx:2.9.1' + implementation 'org.osmdroid:osmdroid-android:6.1.20' // Dagger2 implementation "com.google.dagger:dagger:$dagger_version" @@ -77,11 +77,11 @@ dependencies { compileOnly 'javax.annotation:jsr250-api:1.0' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.assertj:assertj-core:3.25.3' - testImplementation 'io.mockk:mockk:1.13.10' - testImplementation 'org.robolectric:robolectric:4.11.1' + testImplementation 'org.assertj:assertj-core:3.26.3' + testImplementation 'io.mockk:mockk:1.13.12' + testImplementation 'org.robolectric:robolectric:4.13' testImplementation 'androidx.test:core:1.6.1' - androidTestImplementation 'androidx.test:runner:1.6.1' + androidTestImplementation 'androidx.test:runner:1.6.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' } diff --git a/app/src/main/java/org/blitzortung/android/app/Main.kt b/app/src/main/java/org/blitzortung/android/app/Main.kt index e7e4a6bc..f50e4186 100644 --- a/app/src/main/java/org/blitzortung/android/app/Main.kt +++ b/app/src/main/java/org/blitzortung/android/app/Main.kt @@ -745,9 +745,9 @@ class Main : FragmentActivity(), OnSharedPreferenceChangeListener { companion object { val byProviderName: Map = - values().groupBy { it.providerName }.mapValues { it.value.first() } + entries.groupBy { it.providerName }.mapValues { it.value.first() } val byOrdinal: Map = - values().groupBy { it.ordinal }.mapValues { it.value.first() } + entries.groupBy { it.ordinal }.mapValues { it.value.first() } } } diff --git a/app/src/main/java/org/blitzortung/android/app/components/ChangeLogComponent.kt b/app/src/main/java/org/blitzortung/android/app/components/ChangeLogComponent.kt index 7e6818e4..e13f6b77 100644 --- a/app/src/main/java/org/blitzortung/android/app/components/ChangeLogComponent.kt +++ b/app/src/main/java/org/blitzortung/android/app/components/ChangeLogComponent.kt @@ -30,6 +30,6 @@ class ChangeLogComponent @Inject constructor( } fun showChangeLogDialog(context: Context) { - getChangeLogDialog(context)?.show() + getChangeLogDialog(context).show() } } \ No newline at end of file diff --git a/app/src/main/java/org/blitzortung/android/app/controller/NotificationHandler.kt b/app/src/main/java/org/blitzortung/android/app/controller/NotificationHandler.kt index dab7992d..3489a3d7 100644 --- a/app/src/main/java/org/blitzortung/android/app/controller/NotificationHandler.kt +++ b/app/src/main/java/org/blitzortung/android/app/controller/NotificationHandler.kt @@ -59,11 +59,8 @@ open class NotificationHandler @Inject constructor( isAtLeast(Build.VERSION_CODES.O) -> { createNotification(contentIntent, notificationText) } - isAtLeast(Build.VERSION_CODES.JELLY_BEAN) -> { - createJellyBeanNotification(contentIntent, notificationText) - } else -> { - createLegacyNotification(contentIntent, notificationText) + createJellyBeanNotification(contentIntent, notificationText) } } @@ -82,7 +79,6 @@ open class NotificationHandler @Inject constructor( .setShowWhen(true).build() } - @RequiresApi(Build.VERSION_CODES.JELLY_BEAN) private fun createJellyBeanNotification(contentIntent: PendingIntent?, notificationText: String): Notification { val builder = Notification.Builder(context) .setSmallIcon(R.drawable.icon) @@ -113,25 +109,6 @@ open class NotificationHandler @Inject constructor( } } - private fun createLegacyNotification(contentIntent: PendingIntent?, notificationText: String): Notification { - val notification = Notification(R.drawable.icon, notificationText, System.currentTimeMillis()) - val setLatestEventInfo = Notification::class.java.getDeclaredMethod( - "setLatestEventInfo", - Context::class.java, - CharSequence::class.java, - CharSequence::class.java, - PendingIntent::class.java - ) - setLatestEventInfo.invoke( - notification, - context, - context.resources.getText(R.string.app_name), - notificationText, - contentIntent - ) - return notification - } - companion object { const val CHANNEL_ID = "channel" } diff --git a/app/src/main/java/org/blitzortung/android/app/view/PreferenceKey.kt b/app/src/main/java/org/blitzortung/android/app/view/PreferenceKey.kt index 51a68a3f..6f9b6345 100644 --- a/app/src/main/java/org/blitzortung/android/app/view/PreferenceKey.kt +++ b/app/src/main/java/org/blitzortung/android/app/view/PreferenceKey.kt @@ -65,7 +65,7 @@ enum class PreferenceKey(val key: String) { private val stringToValueMap = HashMap() init { - for (key in values()) { + for (key in entries) { val keyString = key.toString() if (keyString in stringToValueMap) { throw IllegalStateException("key value '%s' already defined".format(keyString)) diff --git a/app/src/main/java/org/blitzortung/android/data/FetchBackgroundDataTask.kt b/app/src/main/java/org/blitzortung/android/data/FetchBackgroundDataTask.kt index f8bf4ef0..ce11f5ef 100644 --- a/app/src/main/java/org/blitzortung/android/data/FetchBackgroundDataTask.kt +++ b/app/src/main/java/org/blitzortung/android/data/FetchBackgroundDataTask.kt @@ -18,7 +18,7 @@ internal class FetchBackgroundDataTask( resultConsumer: (ResultEvent) -> Unit, toast: KSuspendFunction1, private val wakeLock: PowerManager.WakeLock -) : FetchDataTask(dataMode, dataProvider, resultConsumer, toast) { +) : FetchDataTask(dataMode, dataProvider, resultConsumer) { override fun onPostExecute(result: ResultEvent?) { super.onPostExecute(result) diff --git a/app/src/main/java/org/blitzortung/android/data/FetchDataTask.kt b/app/src/main/java/org/blitzortung/android/data/FetchDataTask.kt index 9ed5f019..984403b6 100644 --- a/app/src/main/java/org/blitzortung/android/data/FetchDataTask.kt +++ b/app/src/main/java/org/blitzortung/android/data/FetchDataTask.kt @@ -6,13 +6,11 @@ import org.blitzortung.android.app.Main import org.blitzortung.android.data.provider.data.DataProvider import org.blitzortung.android.data.provider.result.ResultEvent import kotlin.coroutines.CoroutineContext -import kotlin.reflect.KSuspendFunction1 internal open class FetchDataTask( private val dataMode: DataMode, private val dataProvider: DataProvider, - private val resultConsumer: (ResultEvent) -> Unit, - private val toast: KSuspendFunction1 + private val resultConsumer: (ResultEvent) -> Unit ) : CoroutineScope { private var job: Job = Job() diff --git a/app/src/main/java/org/blitzortung/android/data/MainDataHandler.kt b/app/src/main/java/org/blitzortung/android/data/MainDataHandler.kt index 1e6c0106..c87b4b1b 100644 --- a/app/src/main/java/org/blitzortung/android/data/MainDataHandler.kt +++ b/app/src/main/java/org/blitzortung/android/data/MainDataHandler.kt @@ -165,7 +165,7 @@ class MainDataHandler @Inject constructor( sendEvent(cachedResult.copy(sequenceNumber = sequenceNumber)) } else { Log.d(LOG_TAG, "MainDataHandler.updateData() fetch $parameters") - FetchDataTask(dataMode, dataProvider!!, { + FetchDataTask(dataMode, dataProvider!!) { if (mode == Mode.ANIMATION) { flags = flags.copy(storeResult = false) if (!it.containsRealtimeData()) { @@ -177,7 +177,7 @@ class MainDataHandler @Inject constructor( cache.put(event.parameters, event) } sendEvent(event.copy(sequenceNumber = sequenceNumber)) - }, ::toast).execute(parameters = parameters, history = history) + }.execute(parameters = parameters, history = history) } } @@ -340,10 +340,6 @@ class MainDataHandler @Inject constructor( val isRealtime: Boolean get() = parameters.isRealtime() - private suspend fun toast(stringResource: Int) = withContext(Dispatchers.Main) { - Toast.makeText(context, stringResource, Toast.LENGTH_LONG).show() - } - private fun broadcastEvent(event: DataEvent) { dataConsumerContainer.broadcast(event) } diff --git a/app/src/main/java/org/blitzortung/android/data/cache/DataCache.kt b/app/src/main/java/org/blitzortung/android/data/cache/DataCache.kt index 4cd00a6a..1c5e41d8 100644 --- a/app/src/main/java/org/blitzortung/android/data/cache/DataCache.kt +++ b/app/src/main/java/org/blitzortung/android/data/cache/DataCache.kt @@ -29,7 +29,7 @@ class DataCache @Inject constructor() { fun calculateTotalSize(): CacheSize = cache.entries.fold(CacheSize(0, 0)) { acc, entry -> val resultEvent = entry.value.value val strikeCount = resultEvent.strikes?.size ?: 0 - Log.v(LOG_TAG, "${entry.key} -> ${strikeCount}") + Log.v(LOG_TAG, "${entry.key} -> $strikeCount") CacheSize(acc.entries + 1, acc.strikes + strikeCount) } diff --git a/app/src/main/java/org/blitzortung/android/data/provider/blitzortung/MapBuilderFactory.kt b/app/src/main/java/org/blitzortung/android/data/provider/blitzortung/MapBuilderFactory.kt index 64679129..a51815c0 100644 --- a/app/src/main/java/org/blitzortung/android/data/provider/blitzortung/MapBuilderFactory.kt +++ b/app/src/main/java/org/blitzortung/android/data/provider/blitzortung/MapBuilderFactory.kt @@ -29,7 +29,7 @@ import javax.inject.Singleton class MapBuilderFailedException(message: String) : Throwable(message) @Singleton -class MapBuilderFactory constructor( +class MapBuilderFactory( private val strikeLineSplitter: (String) -> Array, private val stationLineSplitter: (String) -> Array ) { diff --git a/app/src/main/java/org/blitzortung/android/dialogs/QuickSettingsDialog.kt b/app/src/main/java/org/blitzortung/android/dialogs/QuickSettingsDialog.kt index 71780561..e8af3e1b 100644 --- a/app/src/main/java/org/blitzortung/android/dialogs/QuickSettingsDialog.kt +++ b/app/src/main/java/org/blitzortung/android/dialogs/QuickSettingsDialog.kt @@ -64,22 +64,22 @@ class QuickSettingsDialog : DialogFragment() { @SuppressLint("InflateParams") val view = layoutInflater.inflate(R.layout.quick_settings_dialog, null, false) - val selectedRegionList = view.findViewById(R.id.selected_region) as Spinner + val selectedRegionList: Spinner = view.findViewById(R.id.selected_region) selectedRegionList.setSelection(selectedRegion) - val gridSizeSpinner = view.findViewById(R.id.selected_grid_size) as Spinner + val gridSizeSpinner: Spinner = view.findViewById(R.id.selected_grid_size) gridSizeSpinner.setSelection(selectedGridSize) - val countThresholdSpinner = view.findViewById(R.id.selected_count_threshold) as Spinner + val countThresholdSpinner: Spinner = view.findViewById(R.id.selected_count_threshold) countThresholdSpinner.setSelection(selectedCountThreshold) - val intervalDurationSpinner = view.findViewById(R.id.selected_interval_duration) as Spinner + val intervalDurationSpinner: Spinner = view.findViewById(R.id.selected_interval_duration) intervalDurationSpinner.setSelection(selectedIntervalDuration) - val queryPeriodSpinner = view.findViewById(R.id.selected_query_period) as Spinner + val queryPeriodSpinner: Spinner = view.findViewById(R.id.selected_query_period) queryPeriodSpinner.setSelection(selectedQueryPeriod) - val animationIntervalDuration = view.findViewById(R.id.selected_animation_interval_durations) as Spinner + val animationIntervalDuration: Spinner = view.findViewById(R.id.selected_animation_interval_durations) animationIntervalDuration.setSelection(selectedAnimationInterval) builder.setView(view).setPositiveButton(R.string.ok) { _: DialogInterface, _: Int -> diff --git a/app/src/main/java/org/blitzortung/android/jsonrpc/HttpServiceClientDefault.kt b/app/src/main/java/org/blitzortung/android/jsonrpc/HttpServiceClientDefault.kt index 8a8862d2..4d829c3d 100644 --- a/app/src/main/java/org/blitzortung/android/jsonrpc/HttpServiceClientDefault.kt +++ b/app/src/main/java/org/blitzortung/android/jsonrpc/HttpServiceClientDefault.kt @@ -18,8 +18,6 @@ package org.blitzortung.android.jsonrpc -import android.util.Log -import org.blitzortung.android.app.Main import java.io.InputStreamReader import java.net.HttpURLConnection import java.net.URL diff --git a/app/src/main/java/org/blitzortung/android/map/MapFragment.kt b/app/src/main/java/org/blitzortung/android/map/MapFragment.kt index b69fc1d1..777463db 100644 --- a/app/src/main/java/org/blitzortung/android/map/MapFragment.kt +++ b/app/src/main/java/org/blitzortung/android/map/MapFragment.kt @@ -17,11 +17,13 @@ import org.blitzortung.android.app.view.get import org.osmdroid.tileprovider.tilesource.TileSourceFactory import org.osmdroid.util.GeoPoint import org.osmdroid.views.CustomZoomButtonsController +import org.osmdroid.views.CustomZoomButtonsDisplay import org.osmdroid.views.overlay.CopyrightOverlay import org.osmdroid.views.overlay.ScaleBarOverlay -import org.osmdroid.views.overlay.compass.CompassOverlay +import java.lang.reflect.Field import kotlin.math.min + class MapFragment : Fragment(), OnSharedPreferenceChangeListener { private lateinit var mPrefs: SharedPreferences @@ -57,10 +59,11 @@ class MapFragment : Fragment(), OnSharedPreferenceChangeListener { mCopyrightOverlay = CopyrightOverlay(context) mCopyrightOverlay.setTextSize(7) - mCopyrightOverlay.setOffset(0, bottomOffset) + val copyrightOffset = dm.widthPixels / 2 - dm.widthPixels / 10 + mCopyrightOverlay.setOffset(copyrightOffset, bottomOffset) mapView.overlays.add(mCopyrightOverlay) mScaleBarOverlay = ScaleBarOverlay(mapView) - mScaleBarOverlay.setScaleBarOffset(dm.widthPixels / 2, bottomOffset + ViewHelper.pxFromDp(context, 4f).toInt()) + mScaleBarOverlay.setScaleBarOffset(dm.widthPixels / 2, bottomOffset + ViewHelper.pxFromDp(context, 10f).toInt()) mScaleBarOverlay.setCentred(true) mScaleBarOverlay.setAlignBottom(true) mScaleBarOverlay.setEnableAdjustLength(true) @@ -70,7 +73,9 @@ class MapFragment : Fragment(), OnSharedPreferenceChangeListener { mapView.overlays.add(this.mScaleBarOverlay) //built in zoom controls - mapView.zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER) + mapView.zoomController.setVisibility(CustomZoomButtonsController.Visibility.SHOW_AND_FADEOUT) + + setZoomControllerBottomOffset(bottomOffset + 10) //needed for pinch zooms mapView.setMultiTouchControls(true) @@ -98,6 +103,13 @@ class MapFragment : Fragment(), OnSharedPreferenceChangeListener { onSharedPreferenceChanged(preferences, PreferenceKey.MAP_TYPE, PreferenceKey.MAP_SCALE) } + private fun setZoomControllerBottomOffset(bottomOffset: Int) { + val f: Field = CustomZoomButtonsController::class.java.getDeclaredField("mDisplay") + f.isAccessible = true // Abracadabra + val zoomDisplay = f.get(mapView.zoomController) as CustomZoomButtonsDisplay + zoomDisplay.setAdditionalPixelMargins(0f, 0f, 0f, bottomOffset.toFloat()) + } + fun updateForgroundColor(fgcolor: Int) { mScaleBarOverlay.barPaint = mScaleBarOverlay.barPaint.apply { color = fgcolor } mScaleBarOverlay.textPaint = mScaleBarOverlay.textPaint.apply { color = fgcolor } diff --git a/app/src/main/java/org/blitzortung/android/map/OwnMapView.kt b/app/src/main/java/org/blitzortung/android/map/OwnMapView.kt index d1cdc9e9..39c2fb37 100644 --- a/app/src/main/java/org/blitzortung/android/map/OwnMapView.kt +++ b/app/src/main/java/org/blitzortung/android/map/OwnMapView.kt @@ -50,7 +50,6 @@ class OwnMapView(context: Context) : MapView(context) { private val point: Point = Point() override fun onDoubleTap(event: MotionEvent): Boolean { - this@OwnMapView.removeView(popup) val geoPoint = this.getPoint(event) this@OwnMapView.projection.toPixels(geoPoint, point) diff --git a/app/src/main/java/org/blitzortung/android/map/StrikePopup.kt b/app/src/main/java/org/blitzortung/android/map/StrikePopup.kt index 59826fa1..ae14e072 100644 --- a/app/src/main/java/org/blitzortung/android/map/StrikePopup.kt +++ b/app/src/main/java/org/blitzortung/android/map/StrikePopup.kt @@ -18,7 +18,7 @@ fun createStrikePopUp(popUp: View, strikeOverlay: StrikeOverlay): View { result += " (%.4f %.4f)".format(strikeOverlay.center.longitude, strikeOverlay.center.latitude) } - with(popUp.findViewById(R.id.popup_text) as TextView) { + with(popUp.findViewById(R.id.popup_text)!!) { setBackgroundColor(-2013265920) setPadding(5, 5, 5, 5) text = result diff --git a/app/src/test/java/org/blitzortung/android/data/TimeIntervalTest.kt b/app/src/test/java/org/blitzortung/android/data/TimeIntervalTest.kt index 21aee26a..6d5d74ee 100644 --- a/app/src/test/java/org/blitzortung/android/data/TimeIntervalTest.kt +++ b/app/src/test/java/org/blitzortung/android/data/TimeIntervalTest.kt @@ -52,10 +52,10 @@ class TimeIntervalTest { update { it.rewInterval(history) } - Assertions.assertThat(update { it.ffwdInterval(history) }).isTrue() + assertThat(update { it.ffwdInterval(history) }).isTrue() assertThat(interval.offset).isEqualTo(0) - Assertions.assertThat(update { it.ffwdInterval(history) }).isFalse() + assertThat(update { it.ffwdInterval(history) }).isFalse() assertThat(interval.offset).isEqualTo(0) } @@ -63,12 +63,12 @@ class TimeIntervalTest { fun testGoRealtime() { interval = TimeInterval() - Assertions.assertThat(update { it.goRealtime() }).isFalse() + assertThat(update { it.goRealtime() }).isFalse() assertThat(interval.offset).isEqualTo(0) update { it.rewInterval(history) } - Assertions.assertThat(update { it.goRealtime() }).isTrue() + assertThat(update { it.goRealtime() }).isTrue() assertThat(interval.offset).isEqualTo(0) } diff --git a/app/src/test/java/org/blitzortung/android/data/provider/standard/JsonRpcDataProviderTest.kt b/app/src/test/java/org/blitzortung/android/data/provider/standard/JsonRpcDataProviderTest.kt index f3109373..7b48d39b 100644 --- a/app/src/test/java/org/blitzortung/android/data/provider/standard/JsonRpcDataProviderTest.kt +++ b/app/src/test/java/org/blitzortung/android/data/provider/standard/JsonRpcDataProviderTest.kt @@ -45,7 +45,7 @@ class JsonRpcDataProviderTest { val preferences = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE) val edit = preferences.edit() - edit.put(PreferenceKey.SERVICE_URL, SERVICE_URL); + edit.put(PreferenceKey.SERVICE_URL, SERVICE_URL) edit.apply() uut = JsonRpcDataProvider(preferences, client) @@ -199,8 +199,8 @@ class JsonRpcDataProviderTest { response.put("yd", "30") response.put("xc", "24") response.put("yc", "24") - val strike1 = JSONArray(listOf(2, 0, 5, 10)); - val strike2 = JSONArray(listOf(1, -1, 9, 20)); + val strike1 = JSONArray(listOf(2, 0, 5, 10)) + val strike2 = JSONArray(listOf(1, -1, 9, 20)) val strikesArray = JSONArray(listOf(strike1, strike2)) response.put("r", strikesArray) return response diff --git a/app/src/test/java/org/blitzortung/android/jsonrpc/HttpServiceClientDefaultTest.kt b/app/src/test/java/org/blitzortung/android/jsonrpc/HttpServiceClientDefaultTest.kt index cd866d6c..ca825200 100644 --- a/app/src/test/java/org/blitzortung/android/jsonrpc/HttpServiceClientDefaultTest.kt +++ b/app/src/test/java/org/blitzortung/android/jsonrpc/HttpServiceClientDefaultTest.kt @@ -61,7 +61,7 @@ class HttpServiceClientDefaultTest { val obj = ByteArrayOutputStream() val gzip = GZIPOutputStream(obj) - gzip.write(responseValue.toByteArray()); + gzip.write(responseValue.toByteArray()) gzip.close() handler.response = obj.toByteArray() @@ -82,7 +82,7 @@ class MockURLStreamHandler : URLStreamHandler(), URLStreamHandlerFactory { // *** URLStreamHandler @Throws(IOException::class) - protected override fun openConnection(u: URL?): URLConnection { + override fun openConnection(u: URL?): URLConnection { connection = MockHttpURLConnection(u, responseHeaders) { this.response } return connection } @@ -123,7 +123,7 @@ class MockHttpURLConnection( } override fun setRequestProperty(key: String?, value: String?) { - headers.put(key, value); + headers.put(key, value) } override fun getHeaderField(name: String?): String? { diff --git a/build.gradle b/build.gradle index 907b4638..4ed2b36f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.5.0' apply false - id 'com.android.library' version '8.5.0' apply false - id 'org.jetbrains.kotlin.android' version '1.9.23' apply false + id 'com.android.application' version '8.6.1' apply false + id 'com.android.library' version '8.6.1' apply false + id 'org.jetbrains.kotlin.android' version '2.0.20' apply false id "org.sonarqube" version "5.1.0.4882" }