diff --git a/app/build.gradle b/app/build.gradle index 022ab585..222667ed 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,8 +36,8 @@ android { compileSdk 34 minSdk 26 targetSdk 34 - versionCode 84 - versionName "14.7.0" + versionCode 85 + versionName "15.0.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true diff --git a/app/src/androidTest/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/ExampleInstrumentationTest.kt b/app/src/androidTest/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/ExampleInstrumentationTest.kt index 93ec7949..e0c8a9ed 100644 --- a/app/src/androidTest/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/ExampleInstrumentationTest.kt +++ b/app/src/androidTest/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/ExampleInstrumentationTest.kt @@ -1,23 +1,20 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; +package de.bahnhoefe.deutschlands.bahnhofsfotos -import static org.assertj.core.api.Assertions.assertThat; - -import androidx.test.platform.app.InstrumentationRegistry; - -import org.junit.jupiter.api.Test; +import androidx.test.platform.app.InstrumentationRegistry +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test /** * Instrumentation test, which will execute on an Android device. * - * @see Testing documentation + * @see [Testing documentation](http://d.android.com/tools/testing) */ -public class ExampleInstrumentationTest { - +class ExampleInstrumentationTest { @Test - public void useAppContext() { + fun useAppContext() { // Context of the app under test. - var appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - assertThat(appContext.getPackageName()).isEqualTo("de.bahnhoefe.deutschlands.bahnhofsfotos.debug"); + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertThat(appContext.packageName) + .isEqualTo("de.bahnhoefe.deutschlands.bahnhofsfotos.debug") } } \ No newline at end of file diff --git a/app/src/androidTest/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/TimetableTest.kt b/app/src/androidTest/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/TimetableTest.kt index f3278529..d7399b62 100644 --- a/app/src/androidTest/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/TimetableTest.kt +++ b/app/src/androidTest/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/TimetableTest.kt @@ -1,44 +1,47 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.util; +package de.bahnhoefe.deutschlands.bahnhofsfotos.util -import static org.assertj.core.api.Assertions.assertThat; +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station; - -public class TimetableTest { - - private Station station; +class TimetableTest { + private var station: Station? = null @BeforeEach - public void setUp() { - station = new Station( - "de", - "4711", - "Some Famous Station", - 0.0, - 0.0, - "LOL"); + fun setUp() { + station = Station( + "de", + "4711", + "Some Famous Station", + 0.0, + 0.0, + "LOL" + ) } @Test - public void createTimetableIntentWithId() { - var country = new Country("de", "Deutschland", null, "https://example.com/{id}/blah"); - assertThat(new Timetable().createTimetableIntent(country, station).getData().toString()).isEqualTo("https://example.com/4711/blah"); + fun createTimetableIntentWithId() { + val country = Country("de", "Deutschland", null, "https://example.com/{id}/blah") + assertThat( + Timetable().createTimetableIntent(country, station)!!.data.toString() + ).isEqualTo("https://example.com/4711/blah") } @Test - public void createTimetableIntentWithTitle() { - var country = new Country("de", "Deutschland", null, "https://example.com/{title}/blah"); - assertThat(new Timetable().createTimetableIntent(country, station).getData().toString()).isEqualTo("https://example.com/Some Famous Station/blah"); + fun createTimetableIntentWithTitle() { + val country = Country("de", "Deutschland", null, "https://example.com/{title}/blah") + assertThat( + Timetable().createTimetableIntent(country, station)!!.data.toString() + ).isEqualTo("https://example.com/Some Famous Station/blah") } @Test - public void createTimetableIntentWithDS100() { - var country = new Country("de", "Deutschland", null, "https://example.com/{DS100}/blah"); - assertThat(new Timetable().createTimetableIntent(country, station).getData().toString()).isEqualTo("https://example.com/LOL/blah"); + fun createTimetableIntentWithDS100() { + val country = Country("de", "Deutschland", null, "https://example.com/{DS100}/blah") + assertThat( + Timetable().createTimetableIntent(country, station)!!.data.toString() + ).isEqualTo("https://example.com/LOL/blah") } - } \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/BaseApplication.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/BaseApplication.kt index 269ea138..c3df1dfa 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/BaseApplication.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/BaseApplication.kt @@ -1,444 +1,400 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; - -import android.annotation.SuppressLint; -import android.app.Application; -import android.content.Context; -import android.content.SharedPreferences; -import android.location.Location; -import android.net.Uri; -import android.os.Build; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.multidex.MultiDex; -import androidx.security.crypto.EncryptedSharedPreferences; -import androidx.security.crypto.MasterKey; - -import org.apache.commons.lang3.StringUtils; -import org.mapsforge.core.model.LatLong; -import org.mapsforge.core.model.MapPosition; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.db.DbAdapter; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.License; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Profile; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.UpdatePolicy; -import de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi.RSAPIClient; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.ExceptionHandler; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.StationFilter; - -public class BaseApplication extends Application { - - private static final String TAG = BaseApplication.class.getSimpleName(); - private static final Boolean DEFAULT_FIRSTAPPSTART = false; - private static final String DEFAULT = ""; - private static BaseApplication instance; - - public static final String DEFAULT_COUNTRY = "de"; - public static final String PREF_FILE = "APP_PREF_FILE"; - - private DbAdapter dbAdapter; - private RSAPIClient rsapiClient; - private SharedPreferences preferences; - private SharedPreferences encryptedPreferences; - - public BaseApplication() { - setInstance(this); - } - - public DbAdapter getDbAdapter() { - return dbAdapter; - } - - @Override - protected void attachBaseContext(Context base) { - super.attachBaseContext(base); - MultiDex.install(this); +package de.bahnhoefe.deutschlands.bahnhofsfotos + +import android.annotation.SuppressLint +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import android.location.Location +import android.net.Uri +import android.os.Build +import android.util.Log +import androidx.multidex.MultiDex +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey +import de.bahnhoefe.deutschlands.bahnhofsfotos.db.DbAdapter +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.License +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Profile +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.UpdatePolicy +import de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi.RSAPIClient +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.ExceptionHandler +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.StationFilter +import org.apache.commons.lang3.StringUtils +import org.mapsforge.core.model.LatLong +import org.mapsforge.core.model.MapPosition +import java.util.Optional + +class BaseApplication : Application() { + lateinit var dbAdapter: DbAdapter + lateinit var rsapiClient: RSAPIClient + private lateinit var preferences: SharedPreferences + private lateinit var encryptedPreferences: SharedPreferences + + init { + instance = this + } + + override fun attachBaseContext(base: Context) { + super.attachBaseContext(base) + MultiDex.install(this) // handle crashes only outside the crash reporter activity/process - if (!isCrashReportingProcess()) { + if (!isCrashReportingProcess) { Thread.setDefaultUncaughtExceptionHandler( - new ExceptionHandler(this, Thread.getDefaultUncaughtExceptionHandler())); + Thread.getDefaultUncaughtExceptionHandler()?.let { ExceptionHandler(this, it) } + ) } } - private boolean isCrashReportingProcess() { - var processName = ""; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - // Using the same technique as Application.getProcessName() for older devices - // Using reflection since ActivityThread is an internal API - try { - @SuppressLint("PrivateApi") - var activityThread = Class.forName("android.app.ActivityThread"); - @SuppressLint("DiscouragedPrivateApi") - var getProcessName = activityThread.getDeclaredMethod("currentProcessName"); - processName = (String) getProcessName.invoke(null); - } catch (Exception ignored) { + private val isCrashReportingProcess: Boolean + get() { + var processName: String? = "" + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + // Using the same technique as Application.getProcessName() for older devices + // Using reflection since ActivityThread is an internal API + try { + @SuppressLint("PrivateApi") val activityThread = + Class.forName("android.app.ActivityThread") + @SuppressLint("DiscouragedPrivateApi") val getProcessName = + activityThread.getDeclaredMethod("currentProcessName") + processName = getProcessName.invoke(null) as String + } catch (ignored: Exception) { + } + } else { + processName = getProcessName() } - } else { - processName = Application.getProcessName(); + return processName != null && processName.endsWith(":crash") } - return processName != null && processName.endsWith(":crash"); - } - - private static void setInstance(@NonNull BaseApplication application) { - instance = application; - } - public static BaseApplication getInstance() { - return instance; - } - - @Override - public void onCreate() { - super.onCreate(); - dbAdapter = new DbAdapter(this); - dbAdapter.open(); - - preferences = getSharedPreferences(PREF_FILE, MODE_PRIVATE); + override fun onCreate() { + super.onCreate() + dbAdapter = DbAdapter(this) + dbAdapter.open() + preferences = getSharedPreferences(PREF_FILE, MODE_PRIVATE) // Creates the instance for the encrypted preferences. - encryptedPreferences = null; - try { - var masterKey = new MasterKey.Builder(this) - .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) - .build(); - - encryptedPreferences = EncryptedSharedPreferences.create( - this, - "secret_shared_prefs", - masterKey, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ); - - // migrate access token from unencrypted to encrypted preferences - if (!encryptedPreferences.contains(getString(R.string.ACCESS_TOKEN)) - && preferences.contains(getString(R.string.ACCESS_TOKEN))) { - setAccessToken(preferences.getString(getString(R.string.ACCESS_TOKEN), null)); - preferences.edit().remove(getString(R.string.ACCESS_TOKEN)).apply(); - } - } catch (GeneralSecurityException | IOException e) { - Log.w("Unable to create EncryptedSharedPreferences, fallback to unencrypted preferences", e); + encryptedPreferences = try { + val masterKey = MasterKey.Builder(this) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build() + EncryptedSharedPreferences.create( + this, + "secret_shared_prefs", + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } catch (e: Exception) { + Log.w( + TAG, + "Unable to create EncryptedSharedPreferences, fallback to unencrypted preferences", + e + ) + preferences + } + // migrate access token from unencrypted to encrypted preferences + if (encryptedPreferences != preferences + && !encryptedPreferences.contains(getString(R.string.ACCESS_TOKEN)) + && preferences.contains(getString(R.string.ACCESS_TOKEN)) + ) { + accessToken = preferences.getString(getString(R.string.ACCESS_TOKEN), null) + preferences.edit().remove(getString(R.string.ACCESS_TOKEN)).apply() } // migrate photo owner preference to boolean - var photoOwner = preferences.getAll().get(getString(R.string.PHOTO_OWNER)); - if ("YES".equals(photoOwner)) { - setPhotoOwner(true); + val photoOwner = preferences.all[getString(R.string.PHOTO_OWNER)] + if ("YES" == photoOwner) { + this.photoOwner = true } - - rsapiClient = new RSAPIClient(getApiUrl(), getString(R.string.rsapiClientId), getAccessToken(), getString(R.string.rsapiRedirectScheme) + "://" + getString(R.string.rsapiRedirectHost)); - } - - public String getApiUrl() { - var apiUri = preferences.getString(getString(R.string.API_URL), null); - return getValidatedApiUrlString(apiUri); + rsapiClient = RSAPIClient( + apiUrl!!, getString(R.string.rsapiClientId), accessToken, getString( + R.string.rsapiRedirectScheme + ) + "://" + getString(R.string.rsapiRedirectHost) + ) } - private static String getValidatedApiUrlString(final String apiUrl) { - var uri = toUri(apiUrl); - if (uri.isPresent()) { - var scheme = uri.get().getScheme(); - if (scheme != null && scheme.matches("https?")) { - return apiUrl + (apiUrl.endsWith("/") ? "" : "/"); - } + var apiUrl: String? + get() { + val apiUri = preferences.getString(getString(R.string.API_URL), null) + return getValidatedApiUrlString(apiUri) + } + set(apiUrl) { + val validatedUrl = getValidatedApiUrlString(apiUrl) + putString(R.string.API_URL, validatedUrl) + rsapiClient.setBaseUrl(validatedUrl) } - return "https://api.railway-stations.org/"; - } - - public void setApiUrl(String apiUrl) { - var validatedUrl = getValidatedApiUrlString(apiUrl); - putString(R.string.API_URL, validatedUrl); - rsapiClient.setBaseUrl(validatedUrl); + private fun putBoolean(key: Int, value: Boolean) { + val editor = preferences.edit() + editor.putBoolean(getString(key), value) + editor.apply() } - private void putBoolean(int key, boolean value) { - var editor = preferences.edit(); - editor.putBoolean(getString(key), value); - editor.apply(); + private fun putString(key: Int, value: String?) { + val editor = preferences.edit() + editor.putString(getString(key), StringUtils.trimToNull(value)) + editor.apply() } - private void putString(int key, String value) { - var editor = preferences.edit(); - editor.putString(getString(key), StringUtils.trimToNull(value)); - editor.apply(); + private fun putStringSet(key: Int, value: Set?) { + val editor = preferences.edit() + editor.putStringSet(getString(key), value) + editor.apply() } - private void putStringSet(int key, Set value) { - var editor = preferences.edit(); - editor.putStringSet(getString(key), value); - editor.apply(); + private fun putLong(key: Int, value: Long) { + val editor = preferences.edit() + editor.putLong(getString(key), value) + editor.apply() } - private void putLong(int key, long value) { - var editor = preferences.edit(); - editor.putLong(getString(key), value); - editor.apply(); + private fun putDouble(key: Int, value: Double) { + val editor = preferences.edit() + editor.putLong(getString(key), java.lang.Double.doubleToRawLongBits(value)) + editor.apply() } - private void putDouble(int key, double value) { - var editor = preferences.edit(); - editor.putLong(getString(key), Double.doubleToRawLongBits(value)); - editor.apply(); + private fun getDouble(key: Int): Double { + return if (!preferences.contains(getString(key))) { + 0.0 + } else java.lang.Double.longBitsToDouble(preferences.getLong(getString(key), 0)) } - private double getDouble(int key) { - if (!preferences.contains(getString(key))) { - return 0.0; + var countryCodes: Set + get() { + val oldCountryCode = + preferences.getString(getString(R.string.COUNTRY), DEFAULT_COUNTRY) + var stringSet = preferences.getStringSet( + getString(R.string.COUNTRIES), + HashSet(setOf(oldCountryCode)) + ) + if (stringSet!!.isEmpty()) { + stringSet = HashSet(setOf(DEFAULT_COUNTRY)) + } + return stringSet } - - return Double.longBitsToDouble(preferences.getLong(getString(key), 0)); - } - - public void setCountryCodes(Set countryCodes) { - putStringSet(R.string.COUNTRIES, countryCodes); - } - - public Set getCountryCodes() { - var oldCountryCode = preferences.getString(getString(R.string.COUNTRY), DEFAULT_COUNTRY); - var stringSet = preferences.getStringSet(getString(R.string.COUNTRIES), new HashSet<>(Collections.singleton(oldCountryCode))); - if (stringSet.isEmpty()) { - stringSet = new HashSet<>(Collections.singleton(DEFAULT_COUNTRY)); + set(countryCodes) { + putStringSet(R.string.COUNTRIES, countryCodes) } - return stringSet; - } - - public void setFirstAppStart(boolean firstAppStart) { - putBoolean(R.string.FIRSTAPPSTART, firstAppStart); - } - - public boolean getFirstAppStart() { - return preferences.getBoolean(getString(R.string.FIRSTAPPSTART), DEFAULT_FIRSTAPPSTART); - } - - public License getLicense() { - return License.byName(preferences.getString(getString(R.string.LICENCE), License.UNKNOWN.toString())); - } - - public void setLicense(License license) { - putString(R.string.LICENCE, license != null ? license.toString() : License.UNKNOWN.toString()); - } - - public UpdatePolicy getUpdatePolicy() { - return UpdatePolicy.byName(preferences.getString(getString(R.string.UPDATE_POLICY), License.UNKNOWN.toString())); - } - - public void setUpdatePolicy(UpdatePolicy updatePolicy) { - putString(R.string.UPDATE_POLICY, updatePolicy.toString()); - } - - public boolean getPhotoOwner() { - return preferences.getBoolean(getString(R.string.PHOTO_OWNER), false); - } - - public void setPhotoOwner(boolean photoOwner) { - putBoolean(R.string.PHOTO_OWNER, photoOwner); - } - - public String getPhotographerLink() { - return preferences.getString(getString(R.string.LINK_TO_PHOTOGRAPHER), DEFAULT); - } - - public void setPhotographerLink(String photographerLink) { - putString(R.string.LINK_TO_PHOTOGRAPHER, photographerLink); - } - - public String getNickname() { - return preferences.getString(getString(R.string.NICKNAME), DEFAULT); - } - - public void setNickname(String nickname) { - putString(R.string.NICKNAME, nickname); - } - - public String getEmail() { - return preferences.getString(getString(R.string.EMAIL), DEFAULT); - } - - public void setEmail(String email) { - putString(R.string.EMAIL, email); - } - - public boolean isEmailVerified() { - return preferences.getBoolean(getString(R.string.PHOTO_OWNER), false); - } - - public void setEmailVerified(boolean emailVerified) { - putBoolean(R.string.PHOTO_OWNER, emailVerified); - } - - public String getAccessToken() { - return encryptedPreferences.getString(getString(R.string.ACCESS_TOKEN), null); - } - - public void setAccessToken(String apiToken) { - var editor = encryptedPreferences.edit(); - editor.putString(getString(R.string.ACCESS_TOKEN), StringUtils.trimToNull(apiToken)); - editor.apply(); - } - - public StationFilter getStationFilter() { - var photoFilter = getOptionalBoolean(R.string.STATION_FILTER_PHOTO); - var activeFilter = getOptionalBoolean(R.string.STATION_FILTER_ACTIVE); - var nicknameFilter = preferences.getString(getString(R.string.STATION_FILTER_NICKNAME), null); - return new StationFilter(photoFilter, activeFilter, nicknameFilter); - } - - private Boolean getOptionalBoolean(int key) { - if (preferences.contains(getString(key))) { - return Boolean.valueOf(preferences.getString(getString(key), "false")); + var firstAppStart: Boolean + get() = preferences.getBoolean(getString(R.string.FIRSTAPPSTART), DEFAULT_FIRSTAPPSTART) + set(firstAppStart) { + putBoolean(R.string.FIRSTAPPSTART, firstAppStart) + } + var license: License? + get() = License.byName( + preferences.getString( + getString(R.string.LICENCE), + License.UNKNOWN.toString() + )!! + ) + set(license) { + putString(R.string.LICENCE, license?.toString() ?: License.UNKNOWN.toString()) + } + var updatePolicy: UpdatePolicy + get() = UpdatePolicy.byName( + preferences.getString( + getString(R.string.UPDATE_POLICY), + License.UNKNOWN.toString() + )!! + ) + set(updatePolicy) { + putString(R.string.UPDATE_POLICY, updatePolicy.toString()) + } + private var photoOwner: Boolean + get() = preferences.getBoolean(getString(R.string.PHOTO_OWNER), false) + set(photoOwner) { + putBoolean(R.string.PHOTO_OWNER, photoOwner) + } + private var photographerLink: String? + get() = preferences.getString(getString(R.string.LINK_TO_PHOTOGRAPHER), DEFAULT) + set(photographerLink) { + putString(R.string.LINK_TO_PHOTOGRAPHER, photographerLink) + } + var nickname: String? + get() = preferences.getString(getString(R.string.NICKNAME), DEFAULT) + set(nickname) { + putString(R.string.NICKNAME, nickname) + } + var email: String? + get() = preferences.getString(getString(R.string.EMAIL), DEFAULT) + set(email) { + putString(R.string.EMAIL, email) + } + private var isEmailVerified: Boolean + get() = preferences.getBoolean(getString(R.string.PHOTO_OWNER), false) + set(emailVerified) { + putBoolean(R.string.PHOTO_OWNER, emailVerified) + } + var accessToken: String? + get() = encryptedPreferences.getString(getString(R.string.ACCESS_TOKEN), null) + set(apiToken) { + val editor = encryptedPreferences.edit() + editor.putString(getString(R.string.ACCESS_TOKEN), StringUtils.trimToNull(apiToken)) + editor.apply() + } + var stationFilter: StationFilter + get() { + val photoFilter = getOptionalBoolean(R.string.STATION_FILTER_PHOTO) + val activeFilter = getOptionalBoolean(R.string.STATION_FILTER_ACTIVE) + val nicknameFilter = + preferences.getString(getString(R.string.STATION_FILTER_NICKNAME), null) + return StationFilter(photoFilter, activeFilter, nicknameFilter) + } + set(stationFilter) { + putString( + R.string.STATION_FILTER_PHOTO, + if (stationFilter.hasPhoto() == null) null else stationFilter.hasPhoto() + .toString() + ) + putString( + R.string.STATION_FILTER_ACTIVE, + if (stationFilter.isActive == null) null else stationFilter.isActive.toString() + ) + putString(R.string.STATION_FILTER_NICKNAME, stationFilter.nickname) } - return null; - } - - public void setStationFilter(StationFilter stationFilter) { - putString(R.string.STATION_FILTER_PHOTO, stationFilter.hasPhoto() == null ? null : stationFilter.hasPhoto().toString()); - putString(R.string.STATION_FILTER_ACTIVE, stationFilter.isActive() == null ? null : stationFilter.isActive().toString()); - putString(R.string.STATION_FILTER_NICKNAME, stationFilter.getNickname()); - } - - public long getLastUpdate() { - return preferences.getLong(getString(R.string.LAST_UPDATE), 0L); - } - - public void setLastUpdate(long lastUpdate) { - putLong(R.string.LAST_UPDATE, lastUpdate); - } - - public void setLocationUpdates(boolean locationUpdates) { - putBoolean(R.string.LOCATION_UPDATES, locationUpdates); - } - - public boolean isLocationUpdates() { - return preferences.getBoolean(getString(R.string.LOCATION_UPDATES), true); - } - - public void setLastMapPosition(MapPosition lastMapPosition) { - putDouble(R.string.LAST_POSITION_LAT, lastMapPosition.latLong.latitude); - putDouble(R.string.LAST_POSITION_LON, lastMapPosition.latLong.longitude); - putLong(R.string.LAST_POSITION_ZOOM, lastMapPosition.zoomLevel); - } - - public MapPosition getLastMapPosition() { - var latLong = new LatLong(getDouble(R.string.LAST_POSITION_LAT), getDouble(R.string.LAST_POSITION_LON)); - return new MapPosition(latLong, (byte) preferences.getLong(getString(R.string.LAST_POSITION_ZOOM), getZoomLevelDefault())); - } - - public Location getLastLocation() { - var location = new Location(""); - location.setLatitude(getDouble(R.string.LAST_POSITION_LAT)); - location.setLongitude(getDouble(R.string.LAST_POSITION_LON)); - return location; - } - - /** - * @return the default starting zoom level if nothing is encoded in the map file. - */ - public byte getZoomLevelDefault() { - return (byte) 12; - } - public boolean getAnonymous() { - return preferences.getBoolean(getString(R.string.ANONYMOUS), false); + private fun getOptionalBoolean(key: Int): Boolean? { + return if (preferences.contains(getString(key))) { + java.lang.Boolean.valueOf(preferences.getString(getString(key), "false")) + } else null } - public void setAnonymous(boolean anonymous) { - putBoolean(R.string.ANONYMOUS, anonymous); - } + var lastUpdate: Long + get() = preferences.getLong(getString(R.string.LAST_UPDATE), 0L) + set(lastUpdate) { + putLong(R.string.LAST_UPDATE, lastUpdate) + } + var isLocationUpdates: Boolean + get() = preferences.getBoolean(getString(R.string.LOCATION_UPDATES), true) + set(locationUpdates) { + putBoolean(R.string.LOCATION_UPDATES, locationUpdates) + } + var lastMapPosition: MapPosition + get() { + val latLong = LatLong( + getDouble(R.string.LAST_POSITION_LAT), + getDouble(R.string.LAST_POSITION_LON) + ) + return MapPosition( + latLong, + preferences.getLong( + getString(R.string.LAST_POSITION_ZOOM), + zoomLevelDefault.toLong() + ).toByte() + ) + } + set(lastMapPosition) { + putDouble(R.string.LAST_POSITION_LAT, lastMapPosition.latLong.latitude) + putDouble(R.string.LAST_POSITION_LON, lastMapPosition.latLong.longitude) + putLong(R.string.LAST_POSITION_ZOOM, lastMapPosition.zoomLevel.toLong()) + } + val lastLocation: Location + get() { + val location = Location("") + location.latitude = getDouble(R.string.LAST_POSITION_LAT) + location.longitude = getDouble(R.string.LAST_POSITION_LON) + return location + } + val zoomLevelDefault: Byte + /** + * @return the default starting zoom level if nothing is encoded in the map file. + */ + get() = 12.toByte() + var anonymous: Boolean + get() = preferences.getBoolean(getString(R.string.ANONYMOUS), false) + set(anonymous) { + putBoolean(R.string.ANONYMOUS, anonymous) + } + var profile: Profile? + get() = Profile( + nickname, + license, + photoOwner, + anonymous, + photographerLink, + email, + isEmailVerified + ) + set(profile) { + license = profile!!.license + photoOwner = profile.photoOwner + anonymous = profile.anonymous + photographerLink = profile.link + nickname = profile.nickname + email = profile.email + isEmailVerified = profile.emailVerified + } + var map: String? + get() = preferences.getString(getString(R.string.MAP_FILE), null) + set(map) { + putString(R.string.MAP_FILE, map) + } - public void setProfile(Profile profile) { - setLicense(profile.getLicense()); - setPhotoOwner(profile.getPhotoOwner()); - setAnonymous(profile.getAnonymous()); - setPhotographerLink(profile.getLink()); - setNickname(profile.getNickname()); - setEmail(profile.getEmail()); - setEmailVerified(profile.getEmailVerified()); + private fun putUri(key: Int, uri: Uri?) { + putString(key, uri?.toString()) } - public Profile getProfile() { - return new Profile( - getNickname(), - getLicense(), - getPhotoOwner(), - getAnonymous(), - getPhotographerLink(), - getEmail(), - isEmailVerified()); - } + val mapDirectoryUri: Optional + get() = getUri(getString(R.string.MAP_DIRECTORY)) - public RSAPIClient getRsapiClient() { - return rsapiClient; + private fun getUri(key: String): Optional { + return toUri(preferences.getString(key, null)) } - public String getMap() { - return preferences.getString(getString(R.string.MAP_FILE), null); + fun setMapDirectoryUri(mapDirectory: Uri?) { + putUri(R.string.MAP_DIRECTORY, mapDirectory) } - public void setMap(String map) { - putString(R.string.MAP_FILE, map); - } + val mapThemeDirectoryUri: Optional + get() = getUri(getString(R.string.MAP_THEME_DIRECTORY)) - private void putUri(int key, Uri uri) { - putString(key, uri != null ? uri.toString() : null); + fun setMapThemeDirectoryUri(mapThemeDirectory: Uri?) { + putUri(R.string.MAP_THEME_DIRECTORY, mapThemeDirectory) } - public Optional getMapDirectoryUri() { - return getUri(getString(R.string.MAP_DIRECTORY)); - } + val mapThemeUri: Optional + get() = getUri(getString(R.string.MAP_THEME)) - private Optional getUri(String key) { - return toUri(preferences.getString(key, null)); + fun setMapThemeUri(mapTheme: Uri?) { + putUri(R.string.MAP_THEME, mapTheme) } - public static Optional toUri(String uriString) { - try { - return Optional.ofNullable(Uri.parse(uriString)); - } catch (Exception ignored) { - Log.e(TAG, "can't read Uri string " + uriString); + var sortByDistance: Boolean + get() = preferences.getBoolean(getString(R.string.SORT_BY_DISTANCE), false) + set(sortByDistance) { + putBoolean(R.string.SORT_BY_DISTANCE, sortByDistance) + } + val isLoggedIn: Boolean + get() = rsapiClient.hasToken() + + companion object { + private val TAG = BaseApplication::class.java.simpleName + private const val DEFAULT_FIRSTAPPSTART = false + private const val DEFAULT = "" + lateinit var instance: BaseApplication + private set + const val DEFAULT_COUNTRY = "de" + const val PREF_FILE = "APP_PREF_FILE" + + private fun getValidatedApiUrlString(apiUrl: String?): String { + val uri = toUri(apiUrl) + if (uri.isPresent) { + val scheme = uri.get().scheme + if (scheme != null && scheme.matches("https?".toRegex())) { + return apiUrl + if (apiUrl!!.endsWith("/")) "" else "/" + } + } + return "https://api.railway-stations.org/" } - return Optional.empty(); - } - - public void setMapDirectoryUri(Uri mapDirectory) { - putUri(R.string.MAP_DIRECTORY, mapDirectory); - } - - public Optional getMapThemeDirectoryUri() { - return getUri(getString(R.string.MAP_THEME_DIRECTORY)); - } - - public void setMapThemeDirectoryUri(Uri mapThemeDirectory) { - putUri(R.string.MAP_THEME_DIRECTORY, mapThemeDirectory); - } - - public Optional getMapThemeUri() { - return getUri(getString(R.string.MAP_THEME)); - } - - public void setMapThemeUri(Uri mapTheme) { - putUri(R.string.MAP_THEME, mapTheme); - } - - public boolean getSortByDistance() { - return preferences.getBoolean(getString(R.string.SORT_BY_DISTANCE), false); - } - - public void setSortByDistance(boolean sortByDistance) { - putBoolean(R.string.SORT_BY_DISTANCE, sortByDistance); - } - public boolean isLoggedIn() { - return rsapiClient.hasToken(); + fun toUri(uriString: String?): Optional { + try { + return Optional.ofNullable(Uri.parse(uriString)) + } catch (ignored: Exception) { + Log.e(TAG, "can't read Uri string $uriString") + } + return Optional.empty() + } } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/CountryActivity.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/CountryActivity.kt index ce223d83..54e24c98 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/CountryActivity.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/CountryActivity.kt @@ -1,48 +1,52 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; - -import android.os.Bundle; -import android.view.MenuItem; - -import androidx.appcompat.app.AppCompatActivity; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityCountryBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.db.CountryAdapter; - -public class CountryActivity extends AppCompatActivity { - - private CountryAdapter countryAdapter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - var binding = ActivityCountryBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - var cursor = ((BaseApplication) getApplication()).getDbAdapter().getCountryList(); - countryAdapter = new CountryAdapter(this, cursor, 0); - binding.lstCountries.setAdapter(countryAdapter); - binding.lstCountries.setOnItemClickListener((listview, view, position, id) -> countryAdapter.getView(position, view, binding.lstCountries, cursor)); +package de.bahnhoefe.deutschlands.bahnhofsfotos + +import android.R +import android.os.Bundle +import android.view.MenuItem +import android.view.View +import android.widget.AdapterView +import android.widget.AdapterView.OnItemClickListener +import androidx.appcompat.app.AppCompatActivity +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityCountryBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.db.CountryAdapter + +class CountryActivity : AppCompatActivity() { + private var countryAdapter: CountryAdapter? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val binding = ActivityCountryBinding.inflate( + layoutInflater + ) + setContentView(binding.root) + val cursor = (application as BaseApplication).dbAdapter.countryList + countryAdapter = CountryAdapter(this, cursor, 0) + binding.lstCountries.adapter = countryAdapter + binding.lstCountries.onItemClickListener = + OnItemClickListener { listview: AdapterView<*>?, view: View?, position: Int, id: Long -> + countryAdapter!!.getView( + position, + view, + binding.lstCountries, + cursor + ) + } } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.home) { + onBackPressed() + return true } - return false; + return false } - @Override - public void onBackPressed() { - var baseApplication = BaseApplication.getInstance(); - var selectedCountries = countryAdapter.getSelectedCountries(); - - if (!baseApplication.getCountryCodes().equals(selectedCountries)) { - baseApplication.setCountryCodes(selectedCountries); - baseApplication.setLastUpdate(0L); + override fun onBackPressed() { + val baseApplication: BaseApplication = BaseApplication.Companion.getInstance() + val selectedCountries = countryAdapter!!.selectedCountries + if (baseApplication.countryCodes != selectedCountries) { + baseApplication.countryCodes = selectedCountries + baseApplication.lastUpdate = 0L } - super.onBackPressed(); + super.onBackPressed() } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/DetailsActivity.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/DetailsActivity.kt index 04529175..72d5e3e3 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/DetailsActivity.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/DetailsActivity.kt @@ -1,362 +1,351 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; - -import static android.content.Intent.ACTION_VIEW; -import static android.content.Intent.createChooser; - -import android.app.TaskStackBuilder; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Bundle; -import android.text.Html; -import android.text.TextUtils; -import android.text.method.LinkMovementMethod; -import android.util.Log; -import android.view.ContextThemeWrapper; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.activity.OnBackPressedCallback; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.app.ActivityCompat; -import androidx.core.app.NavUtils; -import androidx.core.content.ContextCompat; -import androidx.core.content.FileProvider; -import androidx.viewpager2.widget.ViewPager2; - -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityDetailsBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.StationInfoBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.SimpleDialogs; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PageablePhoto; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Photo; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PhotoStations; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProviderApp; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Upload; -import de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi.RSAPIClient; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.BitmapCache; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.ConnectionUtil; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.FileUtils; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.NavItem; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Timetable; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class DetailsActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback { - - private static final String TAG = DetailsActivity.class.getSimpleName(); - - // Names of Extras that this class reacts to - public static final String EXTRA_STATION = "EXTRA_STATION"; - - private static final String LINK_FORMAT = "%s"; - - private BaseApplication baseApplication; - private RSAPIClient rsapiClient; - private ActivityDetailsBinding binding; - private Station station; - private Set countries; - private String nickname; - private PhotoPagerAdapter photoPagerAdapter; - private final Map photoBitmaps = new HashMap<>(); - private PageablePhoto selectedPhoto; - private final List carouselPageIndicators = new ArrayList<>(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ActivityDetailsBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - baseApplication = (BaseApplication) getApplication(); - rsapiClient = baseApplication.getRsapiClient(); - countries = baseApplication.getDbAdapter().fetchCountriesWithProviderApps(baseApplication.getCountryCodes()); - - Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); - - photoPagerAdapter = new PhotoPagerAdapter(this); - binding.details.viewPager.setAdapter(photoPagerAdapter); - binding.details.viewPager.setCurrentItem(0, false); - binding.details.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { - @Override - public void onPageSelected(int position) { - var pageablePhoto = photoPagerAdapter.getPageablePhotoAtPosition(position); - onPageablePhotoSelected(pageablePhoto, position); +package de.bahnhoefe.deutschlands.bahnhofsfotos + +import android.app.TaskStackBuilder +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import android.os.Bundle +import android.text.Html +import android.text.TextUtils +import android.text.method.LinkMovementMethod +import android.util.Log +import android.view.ContextThemeWrapper +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.ImageView +import android.widget.TextView +import android.widget.Toast +import androidx.activity.OnBackPressedCallback +import androidx.activity.OnBackPressedDispatcher.addCallback +import androidx.activity.result.ActivityResult +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback +import androidx.core.app.NavUtils +import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider +import androidx.viewpager2.widget.ViewPager2 +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityDetailsBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.StationInfoBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.SimpleDialogs +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country.Companion.getCountryByCode +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PageablePhoto +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Photo +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PhotoStation +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PhotoStations +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProviderApp +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Upload +import de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi.RSAPIClient +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.BitmapAvailableHandler +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.BitmapCache +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.ConnectionUtil +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.FileUtils +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.NavItem +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Timetable +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.util.Locale +import java.util.Objects +import java.util.function.Consumer + +class DetailsActivity : AppCompatActivity(), OnRequestPermissionsResultCallback { + private var baseApplication: BaseApplication? = null + private var rsapiClient: RSAPIClient? = null + private var binding: ActivityDetailsBinding? = null + private var station: Station? = null + private var countries: Set? = null + private var nickname: String? = null + private var photoPagerAdapter: PhotoPagerAdapter? = null + private val photoBitmaps: MutableMap = HashMap() + private var selectedPhoto: PageablePhoto? = null + private val carouselPageIndicators: MutableList? = ArrayList() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityDetailsBinding.inflate( + layoutInflater + ) + setContentView(binding!!.root) + baseApplication = application as BaseApplication + rsapiClient = baseApplication.getRsapiClient() + countries = baseApplication.getDbAdapter() + .fetchCountriesWithProviderApps(baseApplication.getCountryCodes()) + Objects.requireNonNull(supportActionBar).setDisplayHomeAsUpEnabled(true) + photoPagerAdapter = PhotoPagerAdapter(this) + binding!!.details.viewPager.adapter = photoPagerAdapter + binding!!.details.viewPager.setCurrentItem(0, false) + binding!!.details.viewPager.registerOnPageChangeCallback(object : + ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + val pageablePhoto = photoPagerAdapter!!.getPageablePhotoAtPosition(position) + onPageablePhotoSelected(pageablePhoto, position) } - }); + }) // switch off image and license view until we actually have a foto - binding.details.licenseTag.setVisibility(View.INVISIBLE); - binding.details.licenseTag.setMovementMethod(LinkMovementMethod.getInstance()); - - getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - navigateUp(); + binding!!.details.licenseTag.visibility = View.INVISIBLE + binding!!.details.licenseTag.movementMethod = LinkMovementMethod.getInstance() + getOnBackPressedDispatcher().addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + navigateUp() } - }); - - readPreferences(); - onNewIntent(getIntent()); + }) + readPreferences() + onNewIntent(intent) } - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) if (intent != null) { - station = (Station) intent.getSerializableExtra(EXTRA_STATION); - + station = intent.getSerializableExtra(EXTRA_STATION) as Station? if (station == null) { - Log.w(TAG, "EXTRA_STATION in intent data missing"); - Toast.makeText(this, R.string.station_not_found, Toast.LENGTH_LONG).show(); - finish(); - return; + Log.w(TAG, "EXTRA_STATION in intent data missing") + Toast.makeText(this, R.string.station_not_found, Toast.LENGTH_LONG).show() + finish() + return } - - binding.details.marker.setImageDrawable(ContextCompat.getDrawable(this, getMarkerRes())); - - binding.details.tvStationTitle.setText(station.getTitle()); - binding.details.tvStationTitle.setSingleLine(false); - - if (station.hasPhoto()) { + binding!!.details.marker.setImageDrawable(ContextCompat.getDrawable(this, markerRes)) + binding!!.details.tvStationTitle.text = station!!.title + binding!!.details.tvStationTitle.isSingleLine = false + if (station!!.hasPhoto()) { if (ConnectionUtil.checkInternetConnection(this)) { - photoBitmaps.put(station.getPhotoUrl(), null); - BitmapCache.getInstance().getPhoto((bitmap) -> { - if (bitmap != null) { - var pageablePhoto = new PageablePhoto(station, bitmap); - runOnUiThread(() -> { - addIndicator(); - var position = photoPagerAdapter.addPageablePhoto(pageablePhoto); - if (position == 0) { - onPageablePhotoSelected(pageablePhoto, position); + photoBitmaps[station!!.photoUrl] = null + BitmapCache.Companion.getInstance() + .getPhoto(BitmapAvailableHandler { bitmap: Bitmap? -> + if (bitmap != null) { + val pageablePhoto = PageablePhoto(station!!, bitmap) + runOnUiThread { + addIndicator() + val position = + photoPagerAdapter!!.addPageablePhoto(pageablePhoto) + if (position == 0) { + onPageablePhotoSelected(pageablePhoto, position) + } } - }); - } - }, station.getPhotoUrl()); + } + }, station!!.photoUrl) } } - - loadAdditionalPhotos(station); - + loadAdditionalPhotos(station!!) baseApplication.getDbAdapter() - .getPendingUploadsForStation(station) - .forEach(this::addUploadPhoto); + .getPendingUploadsForStation(station) + .forEach(Consumer { upload: Upload? -> addUploadPhoto(upload) }) } - } - private void addUploadPhoto(final Upload upload) { - if (!upload.isPendingPhotoUpload()) { - return; + private fun addUploadPhoto(upload: Upload?) { + if (!upload!!.isPendingPhotoUpload) { + return } - var profile = baseApplication.getProfile(); - var file = FileUtils.getStoredMediaFile(this, upload.getId()); + val profile = baseApplication.getProfile() + val file = FileUtils.getStoredMediaFile(this, upload.id) if (file != null && file.canRead()) { - var bitmap = BitmapFactory.decodeFile(file.getAbsolutePath()); + val bitmap = BitmapFactory.decodeFile(file.absolutePath) if (bitmap != null) { - var pageablePhoto = new PageablePhoto( - upload.getId(), - file.toURI().toString(), - getString(R.string.new_local_photo), - "", - profile.getLicense() != null ? profile.getLicense().getLongName() : "", - "", - bitmap); - runOnUiThread(() -> { - addIndicator(); - var position = photoPagerAdapter.addPageablePhoto(pageablePhoto); + val pageablePhoto = PageablePhoto( + upload.id!!, + file.toURI().toString(), + getString(R.string.new_local_photo), + "", + if (profile!!.license != null) profile!!.license!!.longName else "", + "", + bitmap + ) + runOnUiThread { + addIndicator() + val position = photoPagerAdapter!!.addPageablePhoto(pageablePhoto) if (position == 0) { - onPageablePhotoSelected(pageablePhoto, position); + onPageablePhotoSelected(pageablePhoto, position) } - }); + } } } } - private void loadAdditionalPhotos(final Station station) { - rsapiClient.getPhotoStationById(station.getCountry(), station.getId()).enqueue(new Callback<>() { - - @Override - public void onResponse(@NonNull final Call call, @NonNull final Response response) { - if (response.isSuccessful()) { - var photoStations = response.body(); - if (photoStations == null) { - return; - } - photoStations.getStations().stream() - .flatMap(photoStation -> photoStation.getPhotos().stream()) - .forEach(photo -> { - var url = photoStations.getPhotoBaseUrl() + photo.getPath(); + private fun loadAdditionalPhotos(station: Station) { + rsapiClient!!.getPhotoStationById(station.country, station.id)!! + .enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + val photoStations = response.body() ?: return + photoStations.stations.stream() + .flatMap { (_, _, _, _, _, _, _, photos): PhotoStation -> photos!!.stream() } + .forEach { photo: Photo -> + val url = photoStations.photoBaseUrl + photo.path if (!photoBitmaps.containsKey(url)) { - photoBitmaps.put(url, null); - addIndicator(); - BitmapCache.getInstance().getPhoto((bitmap) -> runOnUiThread(() -> addAdditionalPhotoToPagerAdapter( - photo, - url, - photoStations, - bitmap)), url); + photoBitmaps[url] = null + addIndicator() + BitmapCache.Companion.getInstance() + .getPhoto(BitmapAvailableHandler { bitmap: Bitmap -> + runOnUiThread { + addAdditionalPhotoToPagerAdapter( + photo, + url, + photoStations, + bitmap + ) + } + }, url) } - }); + } + } } - } - @Override - public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { - Log.e(TAG, "Failed to load additional photos", t); - } - }); + override fun onFailure(call: Call, t: Throwable) { + Log.e(TAG, "Failed to load additional photos", t) + } + }) } - private void addAdditionalPhotoToPagerAdapter(Photo photo, String url, PhotoStations photoStations, Bitmap bitmap) { - photoPagerAdapter.addPageablePhoto( - new PageablePhoto( - photo.getId(), - url, - photo.getPhotographer(), - photoStations.getPhotographerUrl(photo.getPhotographer()), - photoStations.getLicenseName(photo.getLicense()), - photoStations.getLicenseUrl(photo.getLicense()), - bitmap) - ); + private fun addAdditionalPhotoToPagerAdapter( + photo: Photo, + url: String, + photoStations: PhotoStations, + bitmap: Bitmap + ) { + photoPagerAdapter!!.addPageablePhoto( + PageablePhoto( + photo.id, + url, + photo.photographer, + photoStations.getPhotographerUrl(photo.photographer), + photoStations.getLicenseName(photo.license), + photoStations.getLicenseUrl(photo.license), + bitmap + ) + ) } - private void addIndicator() { - var indicator = new ImageView(DetailsActivity.this); - indicator.setImageResource(R.drawable.selector_carousel_page_indicator); - indicator.setPadding(0, 0, 5, 0); // left, top, right, bottom - binding.details.llPageIndicatorContainer.addView(indicator); - carouselPageIndicators.add(indicator); + private fun addIndicator() { + val indicator = ImageView(this@DetailsActivity) + indicator.setImageResource(R.drawable.selector_carousel_page_indicator) + indicator.setPadding(0, 0, 5, 0) // left, top, right, bottom + binding!!.details.llPageIndicatorContainer.addView(indicator) + carouselPageIndicators!!.add(indicator) } - private int getMarkerRes() { - if (station == null) { - return R.drawable.marker_missing; - } - if (station.hasPhoto()) { - if (isOwner()) { - return station.getActive() ? R.drawable.marker_violet : R.drawable.marker_violet_inactive; + private val markerRes: Int + private get() { + if (station == null) { + return R.drawable.marker_missing + } + return if (station!!.hasPhoto()) { + if (isOwner) { + if (station!!.active) R.drawable.marker_violet else R.drawable.marker_violet_inactive + } else { + if (station!!.active) R.drawable.marker_green else R.drawable.marker_green_inactive + } } else { - return station.getActive() ? R.drawable.marker_green : R.drawable.marker_green_inactive; + if (station!!.active) R.drawable.marker_red else R.drawable.marker_red_inactive } - } else { - return station.getActive() ? R.drawable.marker_red : R.drawable.marker_red_inactive; } - } - @Override - protected void onResume() { - super.onResume(); - readPreferences(); + override fun onResume() { + super.onResume() + readPreferences() } - private void readPreferences() { - nickname = baseApplication.getNickname(); + private fun readPreferences() { + nickname = baseApplication.getNickname() } - private boolean isOwner() { - return station != null && TextUtils.equals(nickname, station.getPhotographer()); - } + private val isOwner: Boolean + private get() = station != null && TextUtils.equals(nickname, station!!.photographer) - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.details, menu); - return super.onCreateOptionsMenu(menu); + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.details, menu) + return super.onCreateOptionsMenu(menu) } - ActivityResultLauncher activityForResultLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> recreate()); + var activityForResultLauncher = registerForActivityResult( + StartActivityForResult() + ) { result: ActivityResult? -> recreate() } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int itemId = item.getItemId(); + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val itemId = item.itemId if (itemId == R.id.add_photo) { - var intent = new Intent(DetailsActivity.this, UploadActivity.class); - intent.putExtra(UploadActivity.EXTRA_STATION, station); - activityForResultLauncher.launch(intent); + val intent = Intent(this@DetailsActivity, UploadActivity::class.java) + intent.putExtra(UploadActivity.Companion.EXTRA_STATION, station) + activityForResultLauncher.launch(intent) } else if (itemId == R.id.report_problem) { - var intent = new Intent(DetailsActivity.this, ProblemReportActivity.class); - intent.putExtra(ProblemReportActivity.EXTRA_STATION, station); - intent.putExtra(ProblemReportActivity.EXTRA_PHOTO_ID, selectedPhoto != null ? selectedPhoto.getId() : null); - startActivity(intent); + val intent = Intent(this@DetailsActivity, ProblemReportActivity::class.java) + intent.putExtra(ProblemReportActivity.Companion.EXTRA_STATION, station) + intent.putExtra( + ProblemReportActivity.Companion.EXTRA_PHOTO_ID, + if (selectedPhoto != null) selectedPhoto!!.id else null + ) + startActivity(intent) } else if (itemId == R.id.nav_to_station) { - startNavigation(DetailsActivity.this); + startNavigation(this@DetailsActivity) } else if (itemId == R.id.timetable) { - Country.getCountryByCode(countries, station.getCountry()).map(country -> { - var timetableIntent = new Timetable().createTimetableIntent(country, station); - if (timetableIntent != null) { - startActivity(timetableIntent); - } - return null; - }); + getCountryByCode(countries, station!!.country).map { country: Country -> + val timetableIntent = Timetable().createTimetableIntent(country, station) + timetableIntent?.let { startActivity(it) } + null + } } else if (itemId == R.id.share_link) { - var stationUri = Uri.parse(String.format("https://map.railway-stations.org/station.php?countryCode=%s&stationId=%s", station.getCountry(), station.getId())); - startActivity(new Intent(ACTION_VIEW, stationUri)); + val stationUri = Uri.parse( + String.format( + "https://map.railway-stations.org/station.php?countryCode=%s&stationId=%s", + station!!.country, + station!!.id + ) + ) + startActivity(Intent(Intent.ACTION_VIEW, stationUri)) } else if (itemId == R.id.share_photo) { - Country.getCountryByCode(countries, station.getCountry()).map(country -> { - var shareIntent = createPhotoSendIntent(); - if (shareIntent == null) { - return null; - } - shareIntent.putExtra(Intent.EXTRA_TEXT, binding.details.tvStationTitle.getText()); - shareIntent.setType("image/jpeg"); - startActivity(createChooser(shareIntent, "send")); - return null; - }); + getCountryByCode(countries, station!!.country).map { country: Country? -> + val shareIntent = createPhotoSendIntent() ?: return@map null + shareIntent.putExtra(Intent.EXTRA_TEXT, binding!!.details.tvStationTitle.text) + shareIntent.type = "image/jpeg" + startActivity(Intent.createChooser(shareIntent, "send")) + null + } } else if (itemId == R.id.station_info) { - showStationInfo(null); + showStationInfo(null) } else if (itemId == R.id.provider_android_app) { - Country.getCountryByCode(countries, station.getCountry()).map(country -> { - var providerApps = country.getCompatibleProviderApps(); - if (providerApps.size() == 1) { - openAppOrPlayStore(providerApps.get(0), this); - } else if (providerApps.size() > 1) { - var appNames = providerApps.stream() - .map(ProviderApp::getName).toArray(CharSequence[]::new); - SimpleDialogs.simpleSelect(this, getResources().getString(R.string.choose_provider_app), appNames, (dialog, which) -> { - if (which >= 0 && providerApps.size() > which) { - openAppOrPlayStore(providerApps.get(which), DetailsActivity.this); + getCountryByCode(countries, station!!.country).map { country: Country -> + val providerApps = country.compatibleProviderApps + if (providerApps.size == 1) { + openAppOrPlayStore(providerApps[0], this) + } else if (providerApps.size > 1) { + val appNames = providerApps.stream() + .map(ProviderApp::name) + .toArray { _Dummy_.__Array__() } + SimpleDialogs.simpleSelect( + this, + resources.getString(R.string.choose_provider_app), + appNames + ) { dialog: DialogInterface?, which: Int -> + if (which >= 0 && providerApps.size > which) { + openAppOrPlayStore(providerApps[which], this@DetailsActivity) } - }); + } } else { - Toast.makeText(this, R.string.provider_app_missing, Toast.LENGTH_LONG).show(); + Toast.makeText(this, R.string.provider_app_missing, Toast.LENGTH_LONG).show() } - return null; - }); + null + } } else if (itemId == android.R.id.home) { - navigateUp(); + navigateUp() } else { - return super.onOptionsItemSelected(item); + return super.onOptionsItemSelected(item) } - - return true; + return true } /** @@ -364,14 +353,14 @@ public class DetailsActivity extends AppCompatActivity implements ActivityCompat * * @param context activity context */ - public void openAppOrPlayStore(ProviderApp providerApp, Context context) { + fun openAppOrPlayStore(providerApp: ProviderApp, context: Context) { // Try to open App - boolean success = openApp(providerApp, context); + val success = openApp(providerApp, context) // Could not open App, open play store instead if (!success) { - var intent = new Intent(ACTION_VIEW); - intent.setData(Uri.parse(providerApp.getUrl())); - context.startActivity(intent); + val intent = Intent(Intent.ACTION_VIEW) + intent.data = Uri.parse(providerApp.url) + context.startActivity(intent) } } @@ -382,170 +371,190 @@ public class DetailsActivity extends AppCompatActivity implements ActivityCompat * @return true if likely successful, false if unsuccessful * @see https://stackoverflow.com/a/7596063/714965 */ - @SuppressWarnings("JavadocReference") - private boolean openApp(ProviderApp providerApp, Context context) { - if (!providerApp.isAndroid()) { - return false; + private fun openApp(providerApp: ProviderApp, context: Context): Boolean { + if (!providerApp.isAndroid) { + return false } - var manager = context.getPackageManager(); - try { - String packageName = Uri.parse(providerApp.getUrl()).getQueryParameter("id"); - assert packageName != null; - var intent = manager.getLaunchIntentForPackage(packageName); - if (intent == null) { - return false; - } - intent.addCategory(Intent.CATEGORY_LAUNCHER); - context.startActivity(intent); - return true; - } catch (ActivityNotFoundException e) { - return false; + val manager = context.packageManager + return try { + val packageName = Uri.parse(providerApp.url).getQueryParameter("id")!! + val intent = manager.getLaunchIntentForPackage(packageName) ?: return false + intent.addCategory(Intent.CATEGORY_LAUNCHER) + context.startActivity(intent) + true + } catch (e: ActivityNotFoundException) { + false } } - public void navigateUp() { - var callingActivity = getCallingActivity(); // if MapsActivity was calling, then we don't want to rebuild the Backstack - var upIntent = NavUtils.getParentActivityIntent(this); + fun navigateUp() { + val callingActivity = + callingActivity // if MapsActivity was calling, then we don't want to rebuild the Backstack + val upIntent = NavUtils.getParentActivityIntent(this) if (callingActivity == null && upIntent != null) { - upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - if (NavUtils.shouldUpRecreateTask(this, upIntent) || isTaskRoot()) { - Log.v(TAG, "Recreate back stack"); - TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent).startActivities(); + upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + if (NavUtils.shouldUpRecreateTask(this, upIntent) || isTaskRoot) { + Log.v(TAG, "Recreate back stack") + TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent) + .startActivities() } } - - finish(); + finish() } - public void showStationInfo(View view) { - var stationInfoBinding = StationInfoBinding.inflate(getLayoutInflater()); - stationInfoBinding.id.setText(station.getId()); - stationInfoBinding.coordinates.setText(String.format(Locale.US, getResources().getString(R.string.coordinates), station.getLat(), station.getLon())); - stationInfoBinding.active.setText(station != null && station.getActive() ? R.string.active : R.string.inactive); - stationInfoBinding.owner.setText(station != null && station.getPhotographer() != null ? station.getPhotographer() : ""); - if (station.getOutdated()) { - stationInfoBinding.outdatedLabel.setVisibility(View.VISIBLE); + fun showStationInfo(view: View?) { + val stationInfoBinding = StationInfoBinding.inflate( + layoutInflater + ) + stationInfoBinding.id.text = station!!.id + stationInfoBinding.coordinates.text = String.format( + Locale.US, + resources.getString(R.string.coordinates), + station!!.lat, + station!!.lon + ) + stationInfoBinding.active.setText(if (station != null && station!!.active) R.string.active else R.string.inactive) + stationInfoBinding.owner.text = + if (station != null && station!!.photographer != null) station!!.photographer else "" + if (station!!.outdated) { + stationInfoBinding.outdatedLabel.visibility = View.VISIBLE } - - new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.AlertDialogCustom)) - .setTitle(binding.details.tvStationTitle.getText()) - .setView(stationInfoBinding.getRoot()) - .setIcon(R.mipmap.ic_launcher) - .setPositiveButton(android.R.string.ok, null) - .create() - .show(); + AlertDialog.Builder(ContextThemeWrapper(this, R.style.AlertDialogCustom)) + .setTitle(binding!!.details.tvStationTitle.text) + .setView(stationInfoBinding.root) + .setIcon(R.mipmap.ic_launcher) + .setPositiveButton(android.R.string.ok, null) + .create() + .show() } - private Intent createPhotoSendIntent() { + private fun createPhotoSendIntent(): Intent? { if (selectedPhoto != null) { - var sendIntent = new Intent(Intent.ACTION_SEND); - var newFile = FileUtils.getImageCacheFile(getApplicationContext(), String.valueOf(System.currentTimeMillis())); + val sendIntent = Intent(Intent.ACTION_SEND) + val newFile = FileUtils.getImageCacheFile( + applicationContext, System.currentTimeMillis().toString() + ) try { - Log.i(TAG, "Save photo to: " + newFile); - selectedPhoto.getBitmap().compress(Bitmap.CompressFormat.JPEG, Constants.STORED_PHOTO_QUALITY, new FileOutputStream(newFile)); - sendIntent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(DetailsActivity.this, - BuildConfig.APPLICATION_ID + ".fileprovider", newFile)); - return sendIntent; - } catch (FileNotFoundException e) { - Log.e(TAG, "Error saving cached bitmap", e); + Log.i(TAG, "Save photo to: $newFile") + selectedPhoto!!.bitmap!!.compress( + Bitmap.CompressFormat.JPEG, + Constants.STORED_PHOTO_QUALITY, + FileOutputStream(newFile) + ) + sendIntent.putExtra( + Intent.EXTRA_STREAM, FileProvider.getUriForFile( + this@DetailsActivity, + BuildConfig.APPLICATION_ID + ".fileprovider", newFile!! + ) + ) + return sendIntent + } catch (e: FileNotFoundException) { + Log.e(TAG, "Error saving cached bitmap", e) } } - return null; + return null } - private void startNavigation(Context context) { - var adapter = new ArrayAdapter<>(this, android.R.layout.select_dialog_item, - android.R.id.text1, NavItem.values()) { - @NonNull - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - var item = getItem(position); - assert item != null; - - var view = super.getView(position, convertView, parent); - TextView tv = view.findViewById(android.R.id.text1); + private fun startNavigation(context: Context) { + val adapter: ArrayAdapter = object : ArrayAdapter( + this, android.R.layout.select_dialog_item, + android.R.id.text1, NavItem.values() + ) { + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val item = getItem(position)!! + val view = super.getView(position, convertView, parent) + val tv = view.findViewById(android.R.id.text1) //Put the image on the TextView - tv.setCompoundDrawablesWithIntrinsicBounds(item.getIconRes(), 0, 0, 0); - tv.setText(getString(item.getTextRes())); + tv.setCompoundDrawablesWithIntrinsicBounds(item.iconRes, 0, 0, 0) + tv.text = getString(item.textRes) //Add margin between image and text (support various screen densities) - int dp5 = (int) (20 * getResources().getDisplayMetrics().density + 0.5f); - int dp7 = (int) (20 * getResources().getDisplayMetrics().density); - tv.setCompoundDrawablePadding(dp5); - tv.setPadding(dp7, 0, 0, 0); - - return view; + val dp5 = (20 * resources.displayMetrics.density + 0.5f).toInt() + val dp7 = (20 * resources.displayMetrics.density).toInt() + tv.compoundDrawablePadding = dp5 + tv.setPadding(dp7, 0, 0, 0) + return view } - }; - - new AlertDialog.Builder(this) - .setIcon(R.mipmap.ic_launcher) - .setTitle(R.string.navMethod) - .setAdapter(adapter, (dialog, position) -> { - var item = adapter.getItem(position); - assert item != null; - var lat = station.getLat(); - var lon = station.getLon(); - var intent = item.createIntent(DetailsActivity.this, lat, lon, binding.details.tvStationTitle.getText().toString(), getMarkerRes()); - try { - startActivity(intent); - } catch (Exception e) { - Toast.makeText(context, R.string.activitynotfound, Toast.LENGTH_LONG).show(); - } - }).show(); + } + AlertDialog.Builder(this) + .setIcon(R.mipmap.ic_launcher) + .setTitle(R.string.navMethod) + .setAdapter(adapter) { dialog: DialogInterface?, position: Int -> + val item = adapter.getItem(position)!! + val lat = station!!.lat + val lon = station!!.lon + val intent = item.createIntent( + this@DetailsActivity, + lat, + lon, + binding!!.details.tvStationTitle.text.toString(), + markerRes + ) + try { + startActivity(intent) + } catch (e: Exception) { + Toast.makeText(context, R.string.activitynotfound, Toast.LENGTH_LONG).show() + } + }.show() } - public void onPageablePhotoSelected(PageablePhoto pageablePhoto, int position) { - selectedPhoto = pageablePhoto; - binding.details.licenseTag.setVisibility(View.INVISIBLE); - + fun onPageablePhotoSelected(pageablePhoto: PageablePhoto?, position: Int) { + selectedPhoto = pageablePhoto + binding!!.details.licenseTag.visibility = View.INVISIBLE if (pageablePhoto == null) { - return; + return } // Lizenzinfo aufbauen und einblenden - binding.details.licenseTag.setVisibility(View.VISIBLE); - boolean photographerUrlAvailable = pageablePhoto.getPhotographerUrl() != null && !pageablePhoto.getPhotographerUrl().isEmpty(); - boolean licenseUrlAvailable = pageablePhoto.getLicenseUrl() != null && !pageablePhoto.getLicenseUrl().isEmpty(); - - String photographerText; - if (photographerUrlAvailable) { - photographerText = String.format( - LINK_FORMAT, - pageablePhoto.getPhotographerUrl(), - pageablePhoto.getPhotographer()); + binding!!.details.licenseTag.visibility = View.VISIBLE + val photographerUrlAvailable = + pageablePhoto.photographerUrl != null && !pageablePhoto.photographerUrl!!.isEmpty() + val licenseUrlAvailable = + pageablePhoto.licenseUrl != null && !pageablePhoto.licenseUrl!!.isEmpty() + val photographerText: String? + photographerText = if (photographerUrlAvailable) { + String.format( + LINK_FORMAT, + pageablePhoto.photographerUrl, + pageablePhoto.photographer + ) } else { - photographerText = pageablePhoto.getPhotographer(); + pageablePhoto.photographer } - - String licenseText; - if (licenseUrlAvailable) { - licenseText = String.format( - LINK_FORMAT, - pageablePhoto.getLicenseUrl(), - pageablePhoto.getLicense()); + val licenseText: String? + licenseText = if (licenseUrlAvailable) { + String.format( + LINK_FORMAT, + pageablePhoto.licenseUrl, + pageablePhoto.license + ) } else { - licenseText = pageablePhoto.getLicense(); + pageablePhoto.license } - - binding.details.licenseTag.setText( - Html.fromHtml( - String.format( - getText(R.string.license_tag).toString(), - photographerText, - licenseText), Html.FROM_HTML_MODE_LEGACY - ) - ); - + binding!!.details.licenseTag.text = Html.fromHtml( + String.format( + getText(R.string.license_tag).toString(), + photographerText, + licenseText + ), Html.FROM_HTML_MODE_LEGACY + ) if (carouselPageIndicators != null) { - for (int i = 0; i < carouselPageIndicators.size(); i++) { + for (i in carouselPageIndicators.indices) { if (i == position) { - carouselPageIndicators.get(position).setSelected(true); + carouselPageIndicators[position].isSelected = true } else { - carouselPageIndicators.get(i).setSelected(false); + carouselPageIndicators[i].isSelected = false } } } } -} + companion object { + private val TAG = DetailsActivity::class.java.simpleName + + // Names of Extras that this class reacts to + const val EXTRA_STATION = "EXTRA_STATION" + private const val LINK_FORMAT = "%s" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/HighScoreActivity.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/HighScoreActivity.kt index b743154d..05a292bd 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/HighScoreActivity.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/HighScoreActivity.kt @@ -1,134 +1,145 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; - -import android.app.SearchManager; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.view.Menu; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.SearchView; - -import java.util.ArrayList; -import java.util.Collections; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityHighScoreBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.db.HighScoreAdapter; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.HighScore; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.HighScoreItem; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class HighScoreActivity extends AppCompatActivity { - - private static final String TAG = "HighScoreActivity"; - private HighScoreAdapter adapter; - private ActivityHighScoreBinding binding; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ActivityHighScoreBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - var baseApplication = (BaseApplication) getApplication(); - var firstSelectedCountry = baseApplication.getCountryCodes().iterator().next(); - var countries = new ArrayList<>(baseApplication.getDbAdapter().getAllCountries()); - Collections.sort(countries); - countries.add(0, new Country("", getString(R.string.all_countries))); - int selectedItem = 0; - for (var country : countries) { - if (country.getCode().equals(firstSelectedCountry)) { - selectedItem = countries.indexOf(country); +package de.bahnhoefe.deutschlands.bahnhofsfotos + +import android.app.SearchManager +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.View +import android.widget.AdapterView +import android.widget.AdapterView.OnItemClickListener +import android.widget.ArrayAdapter +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SearchView +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityHighScoreBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.db.HighScoreAdapter +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.HighScore +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.HighScoreItem +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.util.Collections + +class HighScoreActivity : AppCompatActivity() { + private var adapter: HighScoreAdapter? = null + private var binding: ActivityHighScoreBinding? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityHighScoreBinding.inflate( + layoutInflater + ) + setContentView(binding!!.root) + val baseApplication = application as BaseApplication + val firstSelectedCountry = baseApplication.countryCodes.iterator().next() + val countries = ArrayList(baseApplication.dbAdapter.allCountries) + Collections.sort(countries) + countries.add(0, Country("", getString(R.string.all_countries))) + var selectedItem = 0 + for (country in countries) { + if (country!!.code == firstSelectedCountry) { + selectedItem = countries.indexOf(country) } } - - var countryAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, countries.toArray(new Country[0])); - binding.countries.setAdapter(countryAdapter); - binding.countries.setSelection(selectedItem); - binding.countries.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - loadHighScore(baseApplication, (Country) parent.getSelectedItem()); + val countryAdapter = ArrayAdapter( + this, + android.R.layout.simple_spinner_dropdown_item, + countries.toTypedArray() + ) + binding!!.countries.adapter = countryAdapter + binding!!.countries.setSelection(selectedItem) + binding!!.countries.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>, + view: View, + position: Int, + id: Long + ) { + loadHighScore(baseApplication, parent.selectedItem as Country) } - @Override - public void onNothingSelected(AdapterView parent) { - } - }); + override fun onNothingSelected(parent: AdapterView<*>?) {} + } } - private void loadHighScore(BaseApplication baseApplication, Country selectedCountry) { - var rsapi = baseApplication.getRsapiClient(); - var highScoreCall = selectedCountry.getCode().isEmpty() ? rsapi.getHighScore() : rsapi.getHighScore(selectedCountry.getCode()); - highScoreCall.enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - if (response.isSuccessful()) { - adapter = new HighScoreAdapter(HighScoreActivity.this, response.body().getItems()); - binding.highscoreList.setAdapter(adapter); - binding.highscoreList.setOnItemClickListener((adapter, v, position, arg3) -> { - var highScoreItem = (HighScoreItem) adapter.getItemAtPosition(position); - var stationFilter = baseApplication.getStationFilter(); - stationFilter.setNickname(highScoreItem.getName()); - baseApplication.setStationFilter(stationFilter); - var intent = new Intent(HighScoreActivity.this, MapsActivity.class); - startActivity(intent); - }); + private fun loadHighScore(baseApplication: BaseApplication, selectedCountry: Country) { + val rsapi = baseApplication.rsapiClient + val highScoreCall = + if (selectedCountry.code.isEmpty()) rsapi!!.highScore else rsapi!!.getHighScore( + selectedCountry.code + ) + highScoreCall!!.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + adapter = HighScoreAdapter(this@HighScoreActivity, response.body()!!.getItems()) + binding!!.highscoreList.adapter = adapter + binding!!.highscoreList.onItemClickListener = + OnItemClickListener { adapter: AdapterView<*>, v: View?, position: Int, arg3: Long -> + val (name) = adapter.getItemAtPosition(position) as HighScoreItem + val stationFilter = baseApplication.stationFilter + stationFilter.nickname = name + baseApplication.stationFilter = stationFilter + val intent = Intent(this@HighScoreActivity, MapsActivity::class.java) + startActivity(intent) + } } } - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Log.e(TAG, "Error loading highscore", t); - Toast.makeText(getBaseContext(), getString(R.string.error_loading_highscore) + t.getMessage(), Toast.LENGTH_LONG).show(); + override fun onFailure(call: Call, t: Throwable) { + Log.e(TAG, "Error loading highscore", t) + Toast.makeText( + baseContext, + getString(R.string.error_loading_highscore) + t.message, + Toast.LENGTH_LONG + ).show() } - }); + }) } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_high_score, menu); - - var manager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); - var search = (SearchView) menu.findItem(R.id.search).getActionView(); - search.setSearchableInfo(manager.getSearchableInfo(getComponentName())); - search.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - - @Override - public boolean onQueryTextSubmit(String s) { - Log.d(TAG, "onQueryTextSubmit "); + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_high_score, menu) + val manager = getSystemService(SEARCH_SERVICE) as SearchManager + val search = menu.findItem(R.id.search).actionView as SearchView? + search!!.setSearchableInfo(manager.getSearchableInfo(componentName)) + search.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(s: String): Boolean { + Log.d(TAG, "onQueryTextSubmit ") if (adapter != null) { - adapter.getFilter().filter(s); - if (adapter.isEmpty()) { - Toast.makeText(HighScoreActivity.this, R.string.no_records_found, Toast.LENGTH_LONG).show(); + adapter!!.filter.filter(s) + if (adapter!!.isEmpty) { + Toast.makeText( + this@HighScoreActivity, + R.string.no_records_found, + Toast.LENGTH_LONG + ).show() } else { - Toast.makeText(HighScoreActivity.this, getResources().getQuantityString(R.plurals.records_found, adapter.getCount(), adapter.getCount()), Toast.LENGTH_LONG).show(); + Toast.makeText( + this@HighScoreActivity, + resources.getQuantityString( + R.plurals.records_found, + adapter!!.count, + adapter!!.count + ), + Toast.LENGTH_LONG + ).show() } } - return false; + return false } - @Override - public boolean onQueryTextChange(String s) { - Log.d(TAG, "onQueryTextChange "); + override fun onQueryTextChange(s: String): Boolean { + Log.d(TAG, "onQueryTextChange ") if (adapter != null) { - adapter.getFilter().filter(s); + adapter!!.filter.filter(s) } - return false; + return false } - - }); - - return true; + }) + return true } -} + companion object { + private const val TAG = "HighScoreActivity" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/InboxActivity.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/InboxActivity.kt index 47b62c03..5ba94d6a 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/InboxActivity.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/InboxActivity.kt @@ -1,59 +1,65 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; +package de.bahnhoefe.deutschlands.bahnhofsfotos -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.widget.Toast; +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.AdapterView +import android.widget.AdapterView.OnItemClickListener +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityInboxBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.db.InboxAdapter +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PublicInbox +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; - -import java.util.List; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityInboxBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.db.InboxAdapter; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PublicInbox; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class InboxActivity extends AppCompatActivity { - - private static final String TAG = InboxActivity.class.getSimpleName(); - - private InboxAdapter adapter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - var binding = ActivityInboxBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - Call> inboxCall = ((BaseApplication)getApplication()).getRsapiClient().getPublicInbox(); - inboxCall.enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call> call, @NonNull Response> response) { - var body = response.body(); - if (response.isSuccessful() && body != null) { - adapter = new InboxAdapter(InboxActivity.this, body); - binding.inboxList.setAdapter(adapter); - binding.inboxList.setOnItemClickListener((parent, view, position, id) -> { - var inboxItem = body.get(position); - var intent = new Intent(InboxActivity.this, MapsActivity.class); - intent.putExtra(MapsActivity.EXTRAS_LATITUDE, inboxItem.getLat()); - intent.putExtra(MapsActivity.EXTRAS_LONGITUDE, inboxItem.getLon()); - intent.putExtra(MapsActivity.EXTRAS_MARKER, inboxItem.getStationId() == null ? R.drawable.marker_missing : R.drawable.marker_red); - startActivity(intent); - }); +class InboxActivity : AppCompatActivity() { + private var adapter: InboxAdapter? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val binding = ActivityInboxBinding.inflate( + layoutInflater + ) + setContentView(binding.root) + val inboxCall = (application as BaseApplication).rsapiClient.publicInbox + inboxCall!!.enqueue(object : Callback?> { + override fun onResponse( + call: Call?>, + response: Response?> + ) { + val body = response.body() + if (response.isSuccessful && body != null) { + adapter = InboxAdapter(this@InboxActivity, body) + binding.inboxList.adapter = adapter + binding.inboxList.onItemClickListener = + OnItemClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long -> + val (_, _, stationId, lat, lon) = body[position] + val intent = Intent(this@InboxActivity, MapsActivity::class.java) + intent.putExtra(MapsActivity.Companion.EXTRAS_LATITUDE, lat) + intent.putExtra(MapsActivity.Companion.EXTRAS_LONGITUDE, lon) + intent.putExtra( + MapsActivity.Companion.EXTRAS_MARKER, + if (stationId == null) R.drawable.marker_missing else R.drawable.marker_red + ) + startActivity(intent) + } } } - @Override - public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - Log.e(TAG, "Error loading public inbox", t); - Toast.makeText(getBaseContext(), getString(R.string.error_loading_inbox) + t.getMessage(), Toast.LENGTH_LONG).show(); + override fun onFailure(call: Call?>, t: Throwable) { + Log.e(TAG, "Error loading public inbox", t) + Toast.makeText( + baseContext, + getString(R.string.error_loading_inbox) + t.message, + Toast.LENGTH_LONG + ).show() } - }); + }) } + companion object { + private val TAG = InboxActivity::class.java.simpleName + } } \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/IntroSliderActivity.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/IntroSliderActivity.kt index 08ab5855..89861f99 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/IntroSliderActivity.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/IntroSliderActivity.kt @@ -1,155 +1,129 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; - -import android.content.Context; -import android.content.Intent; -import android.graphics.Color; -import android.os.Bundle; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; -import androidx.viewpager.widget.PagerAdapter; -import androidx.viewpager.widget.ViewPager; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityIntroSliderBinding; - -public class IntroSliderActivity extends AppCompatActivity { - - private ActivityIntroSliderBinding binding; - private int[] layouts; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - BaseApplication baseApplication = (BaseApplication) getApplication(); - - getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - - binding = ActivityIntroSliderBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - layouts = new int[]{R.layout.intro_slider1, R.layout.intro_slider2}; - - addBottomDots(0); - changeStatusBarColor(); - var viewPagerAdapter = new ViewPagerAdapter(); - binding.viewPager.setAdapter(viewPagerAdapter); - binding.viewPager.addOnPageChangeListener(viewListener); - - binding.btnSliderSkip.setOnClickListener(v -> { - baseApplication.setFirstAppStart(true); - openMainActivity(); - }); - - binding.btnSliderNext.setOnClickListener(v -> { - int current = getNextItem(); - if (current < layouts.length) { - binding.viewPager.setCurrentItem(current); +package de.bahnhoefe.deutschlands.bahnhofsfotos + +import android.content.Intent +import android.graphics.Color +import android.os.Bundle +import android.text.Html +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.viewpager.widget.PagerAdapter +import androidx.viewpager.widget.ViewPager.OnPageChangeListener +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityIntroSliderBinding + +class IntroSliderActivity : AppCompatActivity() { + private var binding: ActivityIntroSliderBinding? = null + private var layouts: IntArray + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val baseApplication = application as BaseApplication + window.decorView.systemUiVisibility = + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + binding = ActivityIntroSliderBinding.inflate( + layoutInflater + ) + setContentView(binding!!.root) + layouts = intArrayOf(R.layout.intro_slider1, R.layout.intro_slider2) + addBottomDots(0) + changeStatusBarColor() + val viewPagerAdapter = ViewPagerAdapter() + binding!!.viewPager.adapter = viewPagerAdapter + binding!!.viewPager.addOnPageChangeListener(viewListener) + binding!!.btnSliderSkip.setOnClickListener { v: View? -> + baseApplication.firstAppStart = true + openMainActivity() + } + binding!!.btnSliderNext.setOnClickListener { v: View? -> + val current = nextItem + if (current < layouts.size) { + binding!!.viewPager.currentItem = current } else { - openMainActivity(); + openMainActivity() } - }); + } } - private void openMainActivity() { - var intent = new Intent(IntroSliderActivity.this, MainActivity.class); - startActivity(intent); - finish(); + private fun openMainActivity() { + val intent = Intent(this@IntroSliderActivity, MainActivity::class.java) + startActivity(intent) + finish() } - @Override - public void onBackPressed() { - openMainActivity(); + override fun onBackPressed() { + openMainActivity() } - private void addBottomDots(int position) { - var dots = new TextView[layouts.length]; - var colorActive = getResources().getIntArray(R.array.dot_active); - var colorInactive = getResources().getIntArray(R.array.dot_inactive); - binding.layoutDots.removeAllViews(); - - for (int i = 0; i < dots.length; i++) { - dots[i] = new TextView(this); - dots[i].setText(Html.fromHtml("•", Html.FROM_HTML_MODE_LEGACY)); - dots[i].setTextSize(35); - dots[i].setTextColor(colorInactive[position]); - binding.layoutDots.addView(dots[i]); + private fun addBottomDots(position: Int) { + val dots = arrayOfNulls(layouts.size) + val colorActive = resources.getIntArray(R.array.dot_active) + val colorInactive = resources.getIntArray(R.array.dot_inactive) + binding!!.layoutDots.removeAllViews() + for (i in dots.indices) { + dots[i] = TextView(this) + dots[i]!!.text = Html.fromHtml("•", Html.FROM_HTML_MODE_LEGACY) + dots[i]!!.textSize = 35f + dots[i]!!.setTextColor(colorInactive[position]) + binding!!.layoutDots.addView(dots[i]) } - - if (dots.length > 0) { - dots[position].setTextColor(colorActive[position]); + if (dots.size > 0) { + dots[position]!!.setTextColor(colorActive[position]) } } - private int getNextItem() { - return binding.viewPager.getCurrentItem() + 1; - } - - final ViewPager.OnPageChangeListener viewListener = new ViewPager.OnPageChangeListener() { - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - + private val nextItem: Int + private get() = binding!!.viewPager.currentItem + 1 + val viewListener: OnPageChangeListener = object : OnPageChangeListener { + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int + ) { } - @Override - public void onPageSelected(int position) { - var baseApplication = (BaseApplication) getApplication(); - addBottomDots(position); - - if (position == layouts.length - 1) { - binding.btnSliderNext.setText(R.string.proceed); - binding.btnSliderSkip.setVisibility(View.INVISIBLE); - baseApplication.setFirstAppStart(true); + override fun onPageSelected(position: Int) { + val baseApplication = application as BaseApplication + addBottomDots(position) + if (position == layouts.size - 1) { + binding!!.btnSliderNext.setText(R.string.proceed) + binding!!.btnSliderSkip.visibility = View.INVISIBLE + baseApplication.firstAppStart = true } else { - binding.btnSliderNext.setText(R.string.next); - binding.btnSliderSkip.setVisibility(View.VISIBLE); + binding!!.btnSliderNext.setText(R.string.next) + binding!!.btnSliderSkip.visibility = View.VISIBLE } } - @Override - public void onPageScrollStateChanged(int state) { - - } - }; - - private void changeStatusBarColor() { - var window = getWindow(); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(Color.TRANSPARENT); + override fun onPageScrollStateChanged(state: Int) {} } - public class ViewPagerAdapter extends PagerAdapter { + private fun changeStatusBarColor() { + val window = window + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + window.statusBarColor = Color.TRANSPARENT + } - @Override - @NonNull - public Object instantiateItem(@NonNull ViewGroup container, int position) { - var layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - var view = layoutInflater.inflate(layouts[position], container, false); - container.addView(view); - return view; + inner class ViewPagerAdapter : PagerAdapter() { + override fun instantiateItem(container: ViewGroup, position: Int): Any { + val layoutInflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater + val view = layoutInflater.inflate(layouts[position], container, false) + container.addView(view) + return view } - @Override - public void destroyItem(ViewGroup container, int position, @NonNull Object object) { - var view = (View) object; - container.removeView(view); + override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { + val view = `object` as View + container.removeView(view) } - @Override - public int getCount() { - return layouts.length; + override fun getCount(): Int { + return layouts.size } - @Override - public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { - return view == object; + override fun isViewFromObject(view: View, `object`: Any): Boolean { + return view === `object` } } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/MainActivity.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/MainActivity.kt index b822ae45..2bc22eae 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/MainActivity.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/MainActivity.kt @@ -1,500 +1,556 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; - -import android.Manifest; -import android.app.AlertDialog; -import android.app.SearchManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.IBinder; -import android.util.Log; -import android.view.ContextThemeWrapper; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.appcompat.app.ActionBarDrawerToggle; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.SearchView; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.core.view.GravityCompat; - -import com.google.android.material.navigation.NavigationView; - -import java.text.SimpleDateFormat; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityMainBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.db.DbAdapter; -import de.bahnhoefe.deutschlands.bahnhofsfotos.db.StationListAdapter; -import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.AppInfoFragment; -import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.SimpleDialogs; -import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.StationFilterBar; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Statistic; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.UpdatePolicy; -import de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi.RSAPIClient; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.StationFilter; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class MainActivity extends AppCompatActivity implements LocationListener, NavigationView.OnNavigationItemSelectedListener, StationFilterBar.OnChangeListener { - - private static final String DIALOG_TAG = "App Info Dialog"; - private static final long CHECK_UPDATE_INTERVAL = 10 * 60 * 1000; // 10 minutes - private static final String TAG = MainActivity.class.getSimpleName(); - private static final int REQUEST_FINE_LOCATION = 1; - - // The minimum distance to change Updates in meters - private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 1000; - // The minimum time between updates in milliseconds - private static final long MIN_TIME_BW_UPDATES = 500; // minute - - private BaseApplication baseApplication; - private DbAdapter dbAdapter; - - private ActivityMainBinding binding; - - private StationListAdapter stationListAdapter; - private String searchString; - - private NearbyNotificationService.StatusBinder statusBinder; - private RSAPIClient rsapiClient; - private Location myPos; - private LocationManager locationManager; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ActivityMainBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - setSupportActionBar(binding.appBarMain.toolbar); - - baseApplication = (BaseApplication) getApplication(); - dbAdapter = baseApplication.getDbAdapter(); - rsapiClient = baseApplication.getRsapiClient(); - - var toggle = new ActionBarDrawerToggle( - this, binding.drawerLayout, binding.appBarMain.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); - binding.drawerLayout.addDrawerListener(toggle); - toggle.syncState(); - - binding.navView.setNavigationItemSelectedListener(this); - - var header = binding.navView.getHeaderView(0); - TextView tvUpdate = header.findViewById(R.id.tvUpdate); - - if (!baseApplication.getFirstAppStart()) { - startActivity(new Intent(this, IntroSliderActivity.class)); - finish(); +package de.bahnhoefe.deutschlands.bahnhofsfotos + +import android.Manifest +import android.app.AlertDialog +import android.app.SearchManager +import android.content.ComponentName +import android.content.DialogInterface +import android.content.Intent +import android.content.ServiceConnection +import android.content.pm.PackageManager +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.IBinder +import android.util.Log +import android.view.ContextThemeWrapper +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.AdapterView.OnItemClickListener +import android.widget.TextView +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.ActionBarDrawerToggle +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SearchView +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.core.view.GravityCompat +import com.google.android.material.navigation.NavigationView +import de.bahnhoefe.deutschlands.bahnhofsfotos.NearbyNotificationService.StatusBinder +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityMainBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.db.DbAdapter +import de.bahnhoefe.deutschlands.bahnhofsfotos.db.StationListAdapter +import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.AppInfoFragment +import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.SimpleDialogs +import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.StationFilterBar +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Statistic +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.UpdatePolicy +import de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi.RSAPIClient +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.StationFilter +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.text.SimpleDateFormat + +class MainActivity : AppCompatActivity(), LocationListener, + NavigationView.OnNavigationItemSelectedListener, StationFilterBar.OnChangeListener { + private lateinit var baseApplication: BaseApplication + private lateinit var dbAdapter: DbAdapter + private lateinit var binding: ActivityMainBinding + private var stationListAdapter: StationListAdapter? = null + private var searchString: String? = null + private var statusBinder: StatusBinder? = null + private lateinit var rsapiClient: RSAPIClient + private var myPos: Location? = null + private var locationManager: LocationManager? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate( + layoutInflater + ) + setContentView(binding.root) + setSupportActionBar(binding.appBarMain.toolbar) + baseApplication = application as BaseApplication + dbAdapter = baseApplication.dbAdapter + rsapiClient = baseApplication.rsapiClient + val toggle = ActionBarDrawerToggle( + this, + binding.drawerLayout, + binding.appBarMain.toolbar, + R.string.navigation_drawer_open, + R.string.navigation_drawer_close + ) + binding.drawerLayout.addDrawerListener(toggle) + toggle.syncState() + binding.navView.setNavigationItemSelectedListener(this) + val header = binding.navView.getHeaderView(0) + val tvUpdate = header.findViewById(R.id.tvUpdate) + if (!baseApplication.firstAppStart) { + startActivity(Intent(this, IntroSliderActivity::class.java)) + finish() } - - var lastUpdateDate = baseApplication.getLastUpdate(); + val lastUpdateDate = baseApplication.lastUpdate if (lastUpdateDate > 0) { - tvUpdate.setText(getString(R.string.last_update_at, SimpleDateFormat.getDateTimeInstance().format(lastUpdateDate))); + tvUpdate.text = getString( + R.string.last_update_at, + SimpleDateFormat.getDateTimeInstance().format(lastUpdateDate) + ) } else { - tvUpdate.setText(R.string.no_stations_in_database); + tvUpdate.setText(R.string.no_stations_in_database) } - - var searchIntent = getIntent(); - if (Intent.ACTION_SEARCH.equals(searchIntent.getAction())) { - searchString = searchIntent.getStringExtra(SearchManager.QUERY); + val searchIntent = intent + if (Intent.ACTION_SEARCH == searchIntent.action) { + searchString = searchIntent.getStringExtra(SearchManager.QUERY) + } + myPos = baseApplication.lastLocation + bindToStatus() + binding.appBarMain.main.pullToRefresh.setOnRefreshListener { + runUpdateCountriesAndStations() + binding.appBarMain.main.pullToRefresh.isRefreshing = false } - - myPos = baseApplication.getLastLocation(); - bindToStatus(); - - binding.appBarMain.main.pullToRefresh.setOnRefreshListener(() -> { - runUpdateCountriesAndStations(); - binding.appBarMain.main.pullToRefresh.setRefreshing(false); - }); } - @Override - public void onBackPressed() { + @Deprecated("Deprecated in Java") + override fun onBackPressed() { if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) { - binding.drawerLayout.closeDrawer(GravityCompat.START); + binding.drawerLayout.closeDrawer(GravityCompat.START) } else { - super.onBackPressed(); + super.onBackPressed() } } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.main, menu); - - var manager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); - var searchMenu = menu.findItem(R.id.search); - var search = (SearchView) searchMenu.getActionView(); - search.setSearchableInfo(manager.getSearchableInfo(getComponentName())); - search.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - - @Override - public boolean onQueryTextSubmit(String s) { - Log.d(TAG, "onQueryTextSubmit: " + s); - searchString = s; - updateStationList(); - return false; + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.main, menu) + val manager = getSystemService(SEARCH_SERVICE) as SearchManager + val searchMenu = menu.findItem(R.id.search) + val search = searchMenu.actionView as SearchView? + search!!.setSearchableInfo(manager.getSearchableInfo(componentName)) + search.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(s: String): Boolean { + Log.d(TAG, "onQueryTextSubmit: $s") + searchString = s + updateStationList() + return false } - @Override - public boolean onQueryTextChange(String s) { - Log.d(TAG, "onQueryTextChange: " + s); - searchString = s; - updateStationList(); - return false; + override fun onQueryTextChange(s: String): Boolean { + Log.d(TAG, "onQueryTextChange: $s") + searchString = s + updateStationList() + return false } - - }); - - var updatePolicy = baseApplication.getUpdatePolicy(); - menu.findItem(updatePolicy.getId()).setChecked(true); - - return true; + }) + val updatePolicy = baseApplication.updatePolicy + menu.findItem(updatePolicy.id).isChecked = true + return true } - private void updateStationList() { + private fun updateStationList() { try { - var sortByDistance = baseApplication.getSortByDistance() && myPos != null; - var stationCount = dbAdapter.countStations(baseApplication.getCountryCodes()); - var cursor = dbAdapter.getStationsListByKeyword(searchString, baseApplication.getStationFilter(), baseApplication.getCountryCodes(), sortByDistance, myPos); + val sortByDistance = baseApplication.sortByDistance && myPos != null + val stationCount = dbAdapter.countStations(baseApplication.countryCodes) + val cursor = dbAdapter.getStationsListByKeyword( + searchString, + baseApplication.stationFilter, + baseApplication.countryCodes, + sortByDistance, + myPos + ) if (stationListAdapter != null) { - stationListAdapter.swapCursor(cursor); + stationListAdapter!!.swapCursor(cursor) } else { - stationListAdapter = new StationListAdapter(this, cursor, 0); - binding.appBarMain.main.lstStations.setAdapter(stationListAdapter); - - binding.appBarMain.main.lstStations.setOnItemClickListener((listview, view, position, id) -> { - var intentDetails = new Intent(MainActivity.this, DetailsActivity.class); - intentDetails.putExtra(DetailsActivity.EXTRA_STATION, dbAdapter.fetchStationByRowId(id)); - startActivity(intentDetails); - }); + stationListAdapter = StationListAdapter(this, cursor, 0) + binding.appBarMain.main.lstStations.adapter = stationListAdapter + binding.appBarMain.main.lstStations.onItemClickListener = + OnItemClickListener { _, _, _, id -> + val intentDetails = Intent(this@MainActivity, DetailsActivity::class.java) + intentDetails.putExtra( + DetailsActivity.EXTRA_STATION, + dbAdapter.fetchStationByRowId(id) + ) + startActivity(intentDetails) + } } - binding.appBarMain.main.filterResult.setText(getString(R.string.filter_result, stationListAdapter.getCount(), stationCount)); - } catch (Exception e) { - Log.e(TAG, "Unhandled Exception in onQueryTextSubmit", e); + binding.appBarMain.main.filterResult.text = + getString(R.string.filter_result, stationListAdapter!!.count, stationCount) + } catch (e: Exception) { + Log.e(TAG, "Unhandled Exception in onQueryTextSubmit", e) } } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val id = item.itemId // necessary for the update policy submenu - item.setChecked(!item.isChecked()); - - if (id == R.id.rb_update_manual) { - baseApplication.setUpdatePolicy(UpdatePolicy.MANUAL); - } else if (id == R.id.rb_update_automatic) { - baseApplication.setUpdatePolicy(UpdatePolicy.AUTOMATIC); - } else if (id == R.id.rb_update_notify) { - baseApplication.setUpdatePolicy(UpdatePolicy.NOTIFY); - } else if (id == R.id.apiUrl) { - showApiUrlDialog(); - } + item.isChecked = !item.isChecked + when (id) { + R.id.rb_update_manual -> { + baseApplication.updatePolicy = UpdatePolicy.MANUAL + } - return super.onOptionsItemSelected(item); - } + R.id.rb_update_automatic -> { + baseApplication.updatePolicy = UpdatePolicy.AUTOMATIC + } - private void showApiUrlDialog() { - SimpleDialogs.prompt(this, R.string.apiUrl, EditorInfo.TYPE_TEXT_VARIATION_URI, R.string.api_url_hint, baseApplication.getApiUrl(), v -> { - baseApplication.setApiUrl(v); - baseApplication.setLastUpdate(0); - recreate(); - }); - } + R.id.rb_update_notify -> { + baseApplication.updatePolicy = UpdatePolicy.NOTIFY + } - private void setNotificationIcon(boolean active) { - var item = binding.navView.getMenu().findItem(R.id.nav_notification); - item.setIcon(ContextCompat.getDrawable(this, active ? R.drawable.ic_notifications_active_gray_24px : R.drawable.ic_notifications_off_gray_24px)); + R.id.apiUrl -> { + showApiUrlDialog() + } + } + return super.onOptionsItemSelected(item) } - @Override - public boolean onNavigationItemSelected(MenuItem item) { - // Handle navigation view item clicks here. - int id = item.getItemId(); - if (id == R.id.nav_slideshow) { - startActivity(new Intent(this, IntroSliderActivity.class)); - finish(); - } else if (id == R.id.nav_your_data) { - startActivity(new Intent(this, MyDataActivity.class)); - } else if (id == R.id.nav_update_photos) { - runUpdateCountriesAndStations(); - } else if (id == R.id.nav_notification) { - toggleNotification(); - } else if (id == R.id.nav_highscore) { - startActivity(new Intent(this, HighScoreActivity.class)); - } else if (id == R.id.nav_outbox) { - startActivity(new Intent(this, OutboxActivity.class)); - } else if (id == R.id.nav_inbox) { - startActivity(new Intent(this, InboxActivity.class)); - } else if (id == R.id.nav_stations_map) { - startActivity(new Intent(this, MapsActivity.class)); - } else if (id == R.id.nav_web_site) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://railway-stations.org"))); - } else if (id == R.id.nav_email) { - var emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:" + getString(R.string.fab_email))); - emailIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.fab_subject)); - startActivity(Intent.createChooser(emailIntent, getString(R.string.fab_chooser_title))); - } else if (id == R.id.nav_app_info) { - new AppInfoFragment().show(getSupportFragmentManager(), DIALOG_TAG); + private fun showApiUrlDialog() { + SimpleDialogs.prompt( + this, + R.string.apiUrl, + EditorInfo.TYPE_TEXT_VARIATION_URI, + R.string.api_url_hint, + baseApplication.apiUrl + ) { v: String? -> + baseApplication.apiUrl = v + baseApplication.lastUpdate = 0 + recreate() } + } - binding.drawerLayout.closeDrawer(GravityCompat.START); - return true; + private fun setNotificationIcon(active: Boolean) { + val item = binding.navView.menu.findItem(R.id.nav_notification) + item.icon = ContextCompat.getDrawable( + this, + if (active) R.drawable.ic_notifications_active_gray_24px else R.drawable.ic_notifications_off_gray_24px + ) } - private void runUpdateCountriesAndStations() { - binding.appBarMain.main.progressBar.setVisibility(View.VISIBLE); + override fun onNavigationItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.nav_slideshow -> { + startActivity(Intent(this, IntroSliderActivity::class.java)) + finish() + } + + R.id.nav_your_data -> { + startActivity(Intent(this, MyDataActivity::class.java)) + } - rsapiClient.runUpdateCountriesAndStations(this, baseApplication, success -> { - if (success) { - TextView tvUpdate = findViewById(R.id.tvUpdate); - tvUpdate.setText(getString(R.string.last_update_at, SimpleDateFormat.getDateTimeInstance().format(baseApplication.getLastUpdate()))); - updateStationList(); + R.id.nav_update_photos -> { + runUpdateCountriesAndStations() + } + + R.id.nav_notification -> { + toggleNotification() + } + + R.id.nav_highscore -> { + startActivity(Intent(this, HighScoreActivity::class.java)) + } + + R.id.nav_outbox -> { + startActivity(Intent(this, OutboxActivity::class.java)) + } + + R.id.nav_inbox -> { + startActivity(Intent(this, InboxActivity::class.java)) } - binding.appBarMain.main.progressBar.setVisibility(View.GONE); - }); + R.id.nav_stations_map -> { + startActivity(Intent(this, MapsActivity::class.java)) + } + + R.id.nav_web_site -> { + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://railway-stations.org"))) + } + + R.id.nav_email -> { + val emailIntent = + Intent( + Intent.ACTION_SENDTO, + Uri.parse("mailto:" + getString(R.string.fab_email)) + ) + emailIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.fab_subject)) + startActivity( + Intent.createChooser( + emailIntent, + getString(R.string.fab_chooser_title) + ) + ) + } + + R.id.nav_app_info -> { + AppInfoFragment().show(supportFragmentManager, DIALOG_TAG) + } + } + binding.drawerLayout.closeDrawer(GravityCompat.START) + return true } - @Override - protected void onPause() { - super.onPause(); - unregisterLocationManager(); + private fun runUpdateCountriesAndStations() { + binding.appBarMain.main.progressBar.visibility = View.VISIBLE + rsapiClient.runUpdateCountriesAndStations(this, baseApplication) { success: Boolean -> + if (success) { + val tvUpdate = findViewById(R.id.tvUpdate) + tvUpdate.text = getString( + R.string.last_update_at, + SimpleDateFormat.getDateTimeInstance().format(baseApplication.lastUpdate) + ) + updateStationList() + } + binding.appBarMain.main.progressBar.visibility = View.GONE + } } - @Override - public void onResume() { - super.onResume(); + override fun onPause() { + super.onPause() + unregisterLocationManager() + } - for (int i = 0; i < binding.navView.getMenu().size(); i++) { - binding.navView.getMenu().getItem(i).setChecked(false); + public override fun onResume() { + super.onResume() + for (i in 0 until binding.navView.menu.size()) { + binding.navView.menu.getItem(i).isChecked = false } - - if (baseApplication.getLastUpdate() == 0) { - runUpdateCountriesAndStations(); - } else if (System.currentTimeMillis() - baseApplication.getLastUpdate() > CHECK_UPDATE_INTERVAL) { - baseApplication.setLastUpdate(System.currentTimeMillis()); - if (baseApplication.getUpdatePolicy() != UpdatePolicy.MANUAL) { - for (var country : baseApplication.getCountryCodes()) { - rsapiClient.getStatistic(country).enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - if (response.isSuccessful()) { - checkForUpdates(response.body(), country); + if (baseApplication.lastUpdate == 0L) { + runUpdateCountriesAndStations() + } else if (System.currentTimeMillis() - baseApplication.lastUpdate > CHECK_UPDATE_INTERVAL) { + baseApplication.lastUpdate = System.currentTimeMillis() + if (baseApplication.updatePolicy !== UpdatePolicy.MANUAL) { + for (country in baseApplication.countryCodes) { + rsapiClient.getStatistic(country)!!.enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + checkForUpdates(response.body(), country) } } - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Log.e(TAG, "Error loading country statistic", t); + override fun onFailure(call: Call, t: Throwable) { + Log.e(TAG, "Error loading country statistic", t) } - }); + }) } } } - - if (baseApplication.getSortByDistance()) { - registerLocationManager(); + if (baseApplication.sortByDistance) { + registerLocationManager() } - - binding.appBarMain.main.stationFilterBar.init(baseApplication, this); - updateStationList(); + binding.appBarMain.main.stationFilterBar.init(baseApplication, this) + updateStationList() } - private void checkForUpdates(Statistic statistic, String country) { + private fun checkForUpdates(statistic: Statistic?, country: String?) { if (statistic == null) { - return; + return } - - var dbStat = dbAdapter.getStatistic(country); - Log.d(TAG, "DbStat: " + dbStat); - if (statistic.getTotal() != dbStat.getTotal() || statistic.getWithPhoto() != dbStat.getWithPhoto() || statistic.getWithoutPhoto() != dbStat.getWithoutPhoto()) { - if (baseApplication.getUpdatePolicy() == UpdatePolicy.AUTOMATIC) { - runUpdateCountriesAndStations(); + val dbStat = dbAdapter.getStatistic(country) + Log.d(TAG, "DbStat: $dbStat") + if (statistic.total != dbStat!!.total || statistic.withPhoto != dbStat.withPhoto || statistic.withoutPhoto != dbStat.withoutPhoto) { + if (baseApplication.updatePolicy === UpdatePolicy.AUTOMATIC) { + runUpdateCountriesAndStations() } else { - new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.AlertDialogCustom)) - .setIcon(R.mipmap.ic_launcher) - .setTitle(R.string.app_name) - .setMessage(R.string.update_available) - .setCancelable(true) - .setPositiveButton(R.string.button_ok_text, (dialog, which) -> { - runUpdateCountriesAndStations(); - dialog.dismiss(); - }) - .setNegativeButton(R.string.button_cancel_text, (dialog, which) -> dialog.dismiss()) - .create().show(); + AlertDialog.Builder(ContextThemeWrapper(this, R.style.AlertDialogCustom)) + .setIcon(R.mipmap.ic_launcher) + .setTitle(R.string.app_name) + .setMessage(R.string.update_available) + .setCancelable(true) + .setPositiveButton(R.string.button_ok_text) { dialog: DialogInterface, _: Int -> + runUpdateCountriesAndStations() + dialog.dismiss() + } + .setNegativeButton(R.string.button_cancel_text) { dialog: DialogInterface, _: Int -> dialog.dismiss() } + .create().show() } } } - private void bindToStatus() { - var intent = new Intent(this, NearbyNotificationService.class); - intent.setAction(NearbyNotificationService.STATUS_INTERFACE); - if (!this.getApplicationContext().bindService(intent, new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - Log.d(TAG, "Bound to status service of NearbyNotificationService"); - statusBinder = (NearbyNotificationService.StatusBinder) service; - invalidateOptionsMenu(); - } + private fun bindToStatus() { + val intent = Intent(this, NearbyNotificationService::class.java) + intent.action = NearbyNotificationService.Companion.STATUS_INTERFACE + if (!this.applicationContext.bindService(intent, object : ServiceConnection { + override fun onServiceConnected(name: ComponentName, service: IBinder) { + Log.d(TAG, "Bound to status service of NearbyNotificationService") + statusBinder = service as StatusBinder + invalidateOptionsMenu() + } - @Override - public void onServiceDisconnected(ComponentName name) { - Log.d(TAG, "Unbound from status service of NearbyNotificationService"); - statusBinder = null; - invalidateOptionsMenu(); - } - }, 0)) { - Log.e(TAG, "Bind request to statistics interface failed"); + override fun onServiceDisconnected(name: ComponentName) { + Log.d(TAG, "Unbound from status service of NearbyNotificationService") + statusBinder = null + invalidateOptionsMenu() + } + }, 0)) { + Log.e(TAG, "Bind request to statistics interface failed") } } - private final ActivityResultLauncher requestNotificationPermissionLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { - if (!isGranted) { - Toast.makeText(MainActivity.this, R.string.notification_permission_needed, Toast.LENGTH_SHORT).show(); - } - }); - - public void toggleNotification() { - if (statusBinder == null - && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU - && ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - requestNotificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); - return; + private val requestNotificationPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean? -> + if (!isGranted!!) { + Toast.makeText( + this@MainActivity, + R.string.notification_permission_needed, + Toast.LENGTH_SHORT + ).show() + } } - toggleNotificationWithPermissionGranted(); + private fun toggleNotification() { + if (statusBinder == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission( + this, + Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + requestNotificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + return + } + toggleNotificationWithPermissionGranted() } - private void toggleNotificationWithPermissionGranted() { - var intent = new Intent(MainActivity.this, NearbyNotificationService.class); + private fun toggleNotificationWithPermissionGranted() { + val intent = Intent(this@MainActivity, NearbyNotificationService::class.java) if (statusBinder == null) { - startService(intent); - bindToStatus(); - setNotificationIcon(true); + startService(intent) + bindToStatus() + setNotificationIcon(true) } else { - stopService(intent); - setNotificationIcon(false); + stopService(intent) + setNotificationIcon(false) } } - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_FINE_LOCATION) { - Log.i(TAG, "Received response for location permission request."); + Log.i(TAG, "Received response for location permission request.") // Check if the required permission has been granted - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Location permission has been granted - registerLocationManager(); + registerLocationManager() } else { //Permission not granted - baseApplication.setSortByDistance(false); - binding.appBarMain.main.stationFilterBar.setSortOrder(false); + baseApplication.sortByDistance = false + binding.appBarMain.main.stationFilterBar.setSortOrder(false) } } } - public void registerLocationManager() { + private fun registerLocationManager() { try { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED - && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_FINE_LOCATION); - return; + if (ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) != PackageManager.PERMISSION_GRANTED + && ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_COARSE_LOCATION + ) != PackageManager.PERMISSION_GRANTED + ) { + ActivityCompat.requestPermissions( + this, + arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), + REQUEST_FINE_LOCATION + ) + return } - - locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE); + locationManager = + applicationContext.getSystemService(LOCATION_SERVICE) as LocationManager // getting GPS status - var isGPSEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); + val isGPSEnabled = locationManager!!.isProviderEnabled(LocationManager.GPS_PROVIDER) // if GPS Enabled get lat/long using GPS Services if (isGPSEnabled) { - locationManager.requestLocationUpdates( - LocationManager.GPS_PROVIDER, - MIN_TIME_BW_UPDATES, - MIN_DISTANCE_CHANGE_FOR_UPDATES, this); - Log.d(TAG, "GPS Enabled"); + locationManager!!.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + MIN_TIME_BW_UPDATES, + MIN_DISTANCE_CHANGE_FOR_UPDATES.toFloat(), this + ) + Log.d(TAG, "GPS Enabled") if (locationManager != null) { - myPos = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); + myPos = locationManager!!.getLastKnownLocation(LocationManager.GPS_PROVIDER) } } else { // getting network status - var isNetworkEnabled = locationManager - .isProviderEnabled(LocationManager.NETWORK_PROVIDER); + val isNetworkEnabled = locationManager!! + .isProviderEnabled(LocationManager.NETWORK_PROVIDER) // First get location from Network Provider if (isNetworkEnabled) { - locationManager.requestLocationUpdates( - LocationManager.NETWORK_PROVIDER, - MIN_TIME_BW_UPDATES, - MIN_DISTANCE_CHANGE_FOR_UPDATES, this); - Log.d(TAG, "Network Location enabled"); + locationManager!!.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, + MIN_TIME_BW_UPDATES, + MIN_DISTANCE_CHANGE_FOR_UPDATES.toFloat(), this + ) + Log.d(TAG, "Network Location enabled") if (locationManager != null) { - myPos = locationManager - .getLastKnownLocation(LocationManager.NETWORK_PROVIDER); + myPos = locationManager!! + .getLastKnownLocation(LocationManager.NETWORK_PROVIDER) } } } - } catch (Exception e) { - Log.e(TAG, "Error registering LocationManager", e); - var b = new Bundle(); - b.putString("error", "Error registering LocationManager: " + e); - locationManager = null; - baseApplication.setSortByDistance(false); - binding.appBarMain.main.stationFilterBar.setSortOrder(false); - return; + } catch (e: Exception) { + Log.e(TAG, "Error registering LocationManager", e) + val b = Bundle() + b.putString("error", "Error registering LocationManager: $e") + locationManager = null + baseApplication.sortByDistance = false + binding.appBarMain.main.stationFilterBar.setSortOrder(false) + return } - Log.i(TAG, "LocationManager registered"); - onLocationChanged(myPos); + Log.i(TAG, "LocationManager registered") + onLocationChanged(myPos!!) } - private void unregisterLocationManager() { + private fun unregisterLocationManager() { if (locationManager != null) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { - locationManager.removeUpdates(this); + if (ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + locationManager!!.removeUpdates(this) } - locationManager = null; + locationManager = null } - Log.i(TAG, "LocationManager unregistered"); + Log.i(TAG, "LocationManager unregistered") } - @Override - public void onLocationChanged(@NonNull Location location) { - myPos = location; - updateStationList(); + override fun onLocationChanged(location: Location) { + myPos = location + updateStationList() } - @Override - public void stationFilterChanged(StationFilter stationFilter) { - baseApplication.setStationFilter(stationFilter); - updateStationList(); + override fun stationFilterChanged(stationFilter: StationFilter) { + baseApplication.stationFilter = stationFilter + updateStationList() } - @Override - public void sortOrderChanged(boolean sortByDistance) { + override fun sortOrderChanged(sortByDistance: Boolean) { if (sortByDistance) { - registerLocationManager(); + registerLocationManager() } - updateStationList(); + updateStationList() } -} + companion object { + private const val DIALOG_TAG = "App Info Dialog" + private const val CHECK_UPDATE_INTERVAL = (10 * 60 * 1000 // 10 minutes + ).toLong() + private val TAG = MainActivity::class.java.simpleName + private const val REQUEST_FINE_LOCATION = 1 + + // The minimum distance to change Updates in meters + private const val MIN_DISTANCE_CHANGE_FOR_UPDATES: Long = 1000 + + // The minimum time between updates in milliseconds + private const val MIN_TIME_BW_UPDATES: Long = 500 // minute + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/MapsActivity.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/MapsActivity.kt index 1354917f..fe5dd863 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/MapsActivity.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/MapsActivity.kt @@ -1,210 +1,179 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; - -import static android.view.Menu.NONE; - -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.net.Uri; -import android.os.Bundle; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.WindowManager; -import android.widget.CheckBox; -import android.widget.Toast; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.core.content.res.ResourcesCompat; -import androidx.documentfile.provider.DocumentFile; - -import org.mapsforge.core.graphics.Align; -import org.mapsforge.core.graphics.Bitmap; -import org.mapsforge.core.graphics.FontFamily; -import org.mapsforge.core.graphics.FontStyle; -import org.mapsforge.core.graphics.Style; -import org.mapsforge.core.model.LatLong; -import org.mapsforge.core.model.MapPosition; -import org.mapsforge.core.model.Point; -import org.mapsforge.map.android.graphics.AndroidGraphicFactory; -import org.mapsforge.map.android.input.MapZoomControls; -import org.mapsforge.map.android.util.AndroidUtil; -import org.mapsforge.map.datastore.MapDataStore; -import org.mapsforge.map.layer.Layer; -import org.mapsforge.map.layer.cache.TileCache; -import org.mapsforge.map.layer.download.TileDownloadLayer; -import org.mapsforge.map.layer.download.tilesource.AbstractTileSource; -import org.mapsforge.map.layer.download.tilesource.OnlineTileSource; -import org.mapsforge.map.layer.download.tilesource.OpenStreetMapMapnik; -import org.mapsforge.map.layer.overlay.Marker; -import org.mapsforge.map.layer.renderer.TileRendererLayer; -import org.mapsforge.map.model.IMapViewPosition; -import org.mapsforge.map.reader.MapFile; -import org.mapsforge.map.rendertheme.InternalRenderTheme; -import org.mapsforge.map.rendertheme.StreamRenderTheme; -import org.mapsforge.map.rendertheme.XmlRenderTheme; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityMapsBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.db.DbAdapter; -import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.MapInfoFragment; -import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.SimpleDialogs; -import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.StationFilterBar; -import de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge.ClusterManager; -import de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge.DbsTileSource; -import de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge.GeoItem; -import de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge.MarkerBitmap; -import de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge.TapHandler; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Upload; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.StationFilter; - -public class MapsActivity extends AppCompatActivity implements LocationListener, TapHandler, StationFilterBar.OnChangeListener { - - public static final String EXTRAS_LATITUDE = "Extras_Latitude"; - public static final String EXTRAS_LONGITUDE = "Extras_Longitude"; - public static final String EXTRAS_MARKER = "Extras_Marker"; - - // The minimum distance to change Updates in meters - private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 1; // meters - - // The minimum time between updates in milliseconds - private static final long MIN_TIME_BW_UPDATES = 500; // minute - - private static final String TAG = MapsActivity.class.getSimpleName(); - private static final int REQUEST_FINE_LOCATION = 1; - private static final String USER_AGENT = "railway-stations.org-android"; - - private final Map onlineTileSources = new HashMap<>(); - protected Layer layer; - protected ClusterManager clusterer = null; - protected final List tileCaches = new ArrayList<>(); - private LatLong myPos = null; - private CheckBox myLocSwitch = null; - private DbAdapter dbAdapter; - private String nickname; - private BaseApplication baseApplication; - private LocationManager locationManager; - private boolean askedForPermission = false; - private Marker missingMarker; - - private ActivityMapsBinding binding; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - AndroidGraphicFactory.createInstance(this.getApplication()); - - binding = ActivityMapsBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - var window = getWindow(); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(Color.parseColor("#c71c4d")); - - setSupportActionBar(binding.mapsToolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - baseApplication = (BaseApplication) getApplication(); - dbAdapter = baseApplication.getDbAdapter(); - nickname = baseApplication.getNickname(); - - var intent = getIntent(); - Marker extraMarker = null; +package de.bahnhoefe.deutschlands.bahnhofsfotos + +import android.Manifest +import android.content.DialogInterface +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import android.net.Uri +import android.os.Bundle +import android.os.VibrationEffect +import android.os.Vibrator +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.WindowManager +import android.widget.CheckBox +import android.widget.CompoundButton +import android.widget.Toast +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.documentfile.provider.DocumentFile +import de.bahnhoefe.deutschlands.bahnhofsfotos.MapsActivity.BahnhofGeoItem +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityMapsBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.db.DbAdapter +import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.MapInfoFragment +import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.SimpleDialogs +import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.StationFilterBar +import de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge.ClusterManager +import de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge.DbsTileSource +import de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge.GeoItem +import de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge.MarkerBitmap +import de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge.TapHandler +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Upload +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.StationFilter +import org.mapsforge.core.graphics.Align +import org.mapsforge.core.graphics.Bitmap +import org.mapsforge.core.graphics.FontFamily +import org.mapsforge.core.graphics.FontStyle +import org.mapsforge.core.graphics.Style +import org.mapsforge.core.model.LatLong +import org.mapsforge.core.model.MapPosition +import org.mapsforge.core.model.Point +import org.mapsforge.map.android.graphics.AndroidGraphicFactory +import org.mapsforge.map.android.input.MapZoomControls +import org.mapsforge.map.android.util.AndroidUtil +import org.mapsforge.map.datastore.MapDataStore +import org.mapsforge.map.layer.Layer +import org.mapsforge.map.layer.cache.TileCache +import org.mapsforge.map.layer.download.TileDownloadLayer +import org.mapsforge.map.layer.download.tilesource.AbstractTileSource +import org.mapsforge.map.layer.download.tilesource.OnlineTileSource +import org.mapsforge.map.layer.download.tilesource.OpenStreetMapMapnik +import org.mapsforge.map.layer.overlay.Marker +import org.mapsforge.map.layer.renderer.TileRendererLayer +import org.mapsforge.map.model.IMapViewPosition +import org.mapsforge.map.reader.MapFile +import org.mapsforge.map.rendertheme.InternalRenderTheme +import org.mapsforge.map.rendertheme.StreamRenderTheme +import org.mapsforge.map.rendertheme.XmlRenderTheme +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.lang.ref.WeakReference +import java.util.Optional +import java.util.function.Function + +class MapsActivity : AppCompatActivity(), LocationListener, TapHandler, + StationFilterBar.OnChangeListener { + private val onlineTileSources: MutableMap = HashMap() + protected var layer: Layer? = null + protected var clusterer: ClusterManager? = null + protected val tileCaches: MutableList = ArrayList() + private var myPos: LatLong? = null + private var myLocSwitch: CheckBox? = null + private var dbAdapter: DbAdapter? = null + private var nickname: String? = null + private var baseApplication: BaseApplication? = null + private var locationManager: LocationManager? = null + private var askedForPermission = false + private var missingMarker: Marker? = null + private var binding: ActivityMapsBinding? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + AndroidGraphicFactory.createInstance(this.application) + binding = ActivityMapsBinding.inflate( + layoutInflater + ) + setContentView(binding!!.root) + val window = window + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + window.statusBarColor = Color.parseColor("#c71c4d") + setSupportActionBar(binding!!.mapsToolbar) + supportActionBar!!.setDisplayHomeAsUpEnabled(true) + baseApplication = application as BaseApplication + dbAdapter = baseApplication.getDbAdapter() + nickname = baseApplication.getNickname() + val intent = intent + var extraMarker: Marker? = null if (intent != null) { - var latitude = (Double) intent.getSerializableExtra(EXTRAS_LATITUDE); - var longitude = (Double) intent.getSerializableExtra(EXTRAS_LONGITUDE); - setMyLocSwitch(false); + val latitude = intent.getSerializableExtra(EXTRAS_LATITUDE) as Double? + val longitude = intent.getSerializableExtra(EXTRAS_LONGITUDE) as Double? + setMyLocSwitch(false) if (latitude != null && longitude != null) { - myPos = new LatLong(latitude, longitude); + myPos = LatLong(latitude, longitude) } - - var markerRes = (Integer) intent.getSerializableExtra(EXTRAS_MARKER); + val markerRes = intent.getSerializableExtra(EXTRAS_MARKER) as Int? if (markerRes != null) { - extraMarker = createBitmapMarker(myPos, markerRes); + extraMarker = createBitmapMarker(myPos, markerRes) } } - - addDBSTileSource(R.string.dbs_osm_basic, "/styles/dbs-osm-basic/"); - addDBSTileSource(R.string.dbs_osm_railway, "/styles/dbs-osm-railway/"); - - createMapViews(); - createTileCaches(); - checkPermissionsAndCreateLayersAndControls(); - + addDBSTileSource(R.string.dbs_osm_basic, "/styles/dbs-osm-basic/") + addDBSTileSource(R.string.dbs_osm_railway, "/styles/dbs-osm-railway/") + createMapViews() + createTileCaches() + checkPermissionsAndCreateLayersAndControls() if (extraMarker != null) { - binding.map.mapView.getLayerManager().getLayers().add(extraMarker); + binding!!.map.mapView.layerManager.layers.add(extraMarker) } } - private void addDBSTileSource(int nameResId, String baseUrl) { - var dbsBasic = new DbsTileSource(getString(nameResId), baseUrl); - onlineTileSources.put(dbsBasic.getName(), dbsBasic); + private fun addDBSTileSource(nameResId: Int, baseUrl: String) { + val dbsBasic = DbsTileSource(getString(nameResId), baseUrl) + onlineTileSources[dbsBasic.name] = dbsBasic } - protected void createTileCaches() { - this.tileCaches.add(AndroidUtil.createTileCache(this, getPersistableId(), - this.binding.map.mapView.getModel().displayModel.getTileSize(), this.getScreenRatio(), - this.binding.map.mapView.getModel().frameBufferModel.getOverdrawFactor(), true)); - } - - /** - * The persistable ID is used to store settings information, like the center of the last view - * and the zoomlevel. By default the simple name of the class is used. The value is not user - * visibile. - * - * @return the id that is used to save this mapview. - */ - protected String getPersistableId() { - return this.getClass().getSimpleName(); + protected fun createTileCaches() { + tileCaches.add( + AndroidUtil.createTileCache( + this, persistableId, + binding!!.map.mapView.model.displayModel.tileSize, screenRatio, + binding!!.map.mapView.model.frameBufferModel.overdrawFactor, true + ) + ) } - /** - * Returns the relative size of a map view in relation to the screen size of the device. This - * is used for cache size calculations. - * By default this returns 1.0, for a full size map view. - * - * @return the screen ratio of the mapview - */ - protected float getScreenRatio() { - return 1.0f; - } + protected val persistableId: String + /** + * The persistable ID is used to store settings information, like the center of the last view + * and the zoomlevel. By default the simple name of the class is used. The value is not user + * visibile. + * + * @return the id that is used to save this mapview. + */ + protected get() = this.javaClass.simpleName + protected val screenRatio: Float + /** + * Returns the relative size of a map view in relation to the screen size of the device. This + * is used for cache size calculations. + * By default this returns 1.0, for a full size map view. + * + * @return the screen ratio of the mapview + */ + protected get() = 1.0f /** * Hook to check for Android Runtime Permissions. */ - protected void checkPermissionsAndCreateLayersAndControls() { - createLayers(); - createControls(); + protected fun checkPermissionsAndCreateLayersAndControls() { + createLayers() + createControls() } /** * Hook to create controls, such as scale bars. * You can add more controls. */ - protected void createControls() { - initializePosition(binding.map.mapView.getModel().mapViewPosition); + protected fun createControls() { + initializePosition(binding!!.map.mapView.model.mapViewPosition) } /** @@ -212,801 +181,855 @@ public class MapsActivity extends AppCompatActivity implements LocationListener, * * @param mvp the map view position to be set */ - protected void initializePosition(IMapViewPosition mvp) { + protected fun initializePosition(mvp: IMapViewPosition) { if (myPos != null) { - mvp.setMapPosition(new MapPosition(myPos, baseApplication.getZoomLevelDefault())); + mvp.mapPosition = MapPosition(myPos, baseApplication.getZoomLevelDefault()) } else { - mvp.setMapPosition(baseApplication.getLastMapPosition()); + mvp.mapPosition = baseApplication.getLastMapPosition() } - mvp.setZoomLevelMax(getZoomLevelMax()); - mvp.setZoomLevelMin(getZoomLevelMin()); + mvp.zoomLevelMax = zoomLevelMax + mvp.zoomLevelMin = zoomLevelMin } /** * Template method to create the map views. */ - protected void createMapViews() { - binding.map.mapView.setClickable(true); - binding.map.mapView.setOnMapDragListener(() -> myLocSwitch.setChecked(false)); - binding.map.mapView.getMapScaleBar().setVisible(true); - binding.map.mapView.setBuiltInZoomControls(true); - binding.map.mapView.getMapZoomControls().setAutoHide(true); - binding.map.mapView.getMapZoomControls().setZoomLevelMin(getZoomLevelMin()); - binding.map.mapView.getMapZoomControls().setZoomLevelMax(getZoomLevelMax()); - - binding.map.mapView.getMapZoomControls().setZoomControlsOrientation(MapZoomControls.Orientation.VERTICAL_IN_OUT); - binding.map.mapView.getMapZoomControls().setZoomInResource(R.drawable.zoom_control_in); - binding.map.mapView.getMapZoomControls().setZoomOutResource(R.drawable.zoom_control_out); - binding.map.mapView.getMapZoomControls().setMarginHorizontal(getResources().getDimensionPixelOffset(R.dimen.controls_margin)); - binding.map.mapView.getMapZoomControls().setMarginVertical(getResources().getDimensionPixelOffset(R.dimen.controls_margin)); + protected fun createMapViews() { + binding!!.map.mapView.isClickable = true + binding!!.map.mapView.setOnMapDragListener { myLocSwitch!!.isChecked = false } + binding!!.map.mapView.mapScaleBar.isVisible = true + binding!!.map.mapView.setBuiltInZoomControls(true) + binding!!.map.mapView.mapZoomControls.isAutoHide = true + binding!!.map.mapView.mapZoomControls.zoomLevelMin = zoomLevelMin + binding!!.map.mapView.mapZoomControls.zoomLevelMax = zoomLevelMax + binding!!.map.mapView.mapZoomControls.setZoomControlsOrientation(MapZoomControls.Orientation.VERTICAL_IN_OUT) + binding!!.map.mapView.mapZoomControls.setZoomInResource(R.drawable.zoom_control_in) + binding!!.map.mapView.mapZoomControls.setZoomOutResource(R.drawable.zoom_control_out) + binding!!.map.mapView.mapZoomControls.setMarginHorizontal( + resources.getDimensionPixelOffset( + R.dimen.controls_margin + ) + ) + binding!!.map.mapView.mapZoomControls.setMarginVertical(resources.getDimensionPixelOffset(R.dimen.controls_margin)) } - protected byte getZoomLevelMax() { - return binding.map.mapView.getModel().mapViewPosition.getZoomLevelMax(); - } - - protected byte getZoomLevelMin() { - return binding.map.mapView.getModel().mapViewPosition.getZoomLevelMin(); - } + protected val zoomLevelMax: Byte + protected get() = binding!!.map.mapView.model.mapViewPosition.zoomLevelMax + protected val zoomLevelMin: Byte + protected get() = binding!!.map.mapView.model.mapViewPosition.zoomLevelMin /** * Hook to purge tile caches. * By default we purge every tile cache that has been added to the tileCaches list. */ - protected void purgeTileCaches() { - for (TileCache tileCache : tileCaches) { - tileCache.purge(); - } - tileCaches.clear(); - } - - protected XmlRenderTheme getRenderTheme() { - var mapTheme = baseApplication.getMapThemeUri(); - return mapTheme.map(m -> { - try { - var renderThemeFile = DocumentFile.fromSingleUri(getApplication(), m); - return new StreamRenderTheme("/assets/", getContentResolver().openInputStream(renderThemeFile.getUri())); - } catch (Exception e) { - Log.e(TAG, "Error loading theme " + mapTheme, e); - return InternalRenderTheme.DEFAULT; - } - }).orElse(InternalRenderTheme.DEFAULT); + protected fun purgeTileCaches() { + for (tileCache in tileCaches) { + tileCache.purge() + } + tileCaches.clear() } - protected Optional getMapFile() { - var mapUri = BaseApplication.toUri(baseApplication.getMap()); - return mapUri.map(map -> { - if (!DocumentFile.isDocumentUri(this, map)) { - return null; - } - try { - var inputStream = (FileInputStream) getContentResolver().openInputStream(map); - return new MapFile(inputStream, 0, null); - } catch (FileNotFoundException e) { - Log.e(TAG, "Can't open mapFile", e); + protected val renderTheme: XmlRenderTheme + protected get() { + val mapTheme = baseApplication.getMapThemeUri() + return mapTheme!!.map { m: Uri? -> + try { + val renderThemeFile = DocumentFile.fromSingleUri(application, m!!) + return@map StreamRenderTheme( + "/assets/", contentResolver.openInputStream( + renderThemeFile!!.uri + ) + ) + } catch (e: Exception) { + Log.e(TAG, "Error loading theme $mapTheme", e) + return@map InternalRenderTheme.DEFAULT + } + }.orElse(InternalRenderTheme.DEFAULT) + } + protected val mapFile: Optional + protected get() { + val mapUri: Optional = BaseApplication.Companion.toUri(baseApplication.getMap()) + return mapUri.map { map: Uri? -> + if (!DocumentFile.isDocumentUri(this, map)) { + return@map null + } + try { + val inputStream = contentResolver.openInputStream(map!!) as FileInputStream? + return@map MapFile(inputStream, 0, null) + } catch (e: FileNotFoundException) { + Log.e(TAG, "Can't open mapFile", e) + } + null } - return null; - }); - } - - protected void createLayers() { - var mapFile = getMapFile(); + } - if (mapFile.isPresent()) { - var rendererLayer = new TileRendererLayer(this.tileCaches.get(0), mapFile.get(), - this.binding.map.mapView.getModel().mapViewPosition, false, true, false, AndroidGraphicFactory.INSTANCE) { - @Override - public boolean onLongPress(LatLong tapLatLong, Point thisXY, - Point tapXY) { - MapsActivity.this.onLongPress(tapLatLong); - return true; + protected fun createLayers() { + val mapFile = mapFile + if (mapFile.isPresent) { + val rendererLayer: TileRendererLayer = object : TileRendererLayer( + tileCaches[0], + mapFile.get(), + binding!!.map.mapView.model.mapViewPosition, + false, + true, + false, + AndroidGraphicFactory.INSTANCE + ) { + override fun onLongPress( + tapLatLong: LatLong, thisXY: Point, + tapXY: Point + ): Boolean { + this@MapsActivity.onLongPress(tapLatLong) + return true } - }; - rendererLayer.setXmlRenderTheme(getRenderTheme()); - this.layer = rendererLayer; - binding.map.mapView.getLayerManager().getLayers().add(this.layer); + } + rendererLayer.setXmlRenderTheme(renderTheme) + layer = rendererLayer + binding!!.map.mapView.layerManager.layers.add(layer) } else { - AbstractTileSource tileSource = onlineTileSources.get(baseApplication.getMap()); - + var tileSource: AbstractTileSource? = onlineTileSources[baseApplication.getMap()] if (tileSource == null) { - tileSource = OpenStreetMapMapnik.INSTANCE; + tileSource = OpenStreetMapMapnik.INSTANCE } - - tileSource.setUserAgent(USER_AGENT); - this.layer = new TileDownloadLayer(this.tileCaches.get(0), - this.binding.map.mapView.getModel().mapViewPosition, tileSource, - AndroidGraphicFactory.INSTANCE) { - @Override - public boolean onLongPress(LatLong tapLatLong, Point thisXY, - Point tapXY) { - MapsActivity.this.onLongPress(tapLatLong); - return true; + tileSource!!.userAgent = USER_AGENT + layer = object : TileDownloadLayer( + tileCaches[0], + binding!!.map.mapView.model.mapViewPosition, tileSource, + AndroidGraphicFactory.INSTANCE + ) { + override fun onLongPress( + tapLatLong: LatLong, thisXY: Point, + tapXY: Point + ): Boolean { + this@MapsActivity.onLongPress(tapLatLong) + return true } - }; - binding.map.mapView.getLayerManager().getLayers().add(this.layer); - - binding.map.mapView.setZoomLevelMin(tileSource.getZoomLevelMin()); - binding.map.mapView.setZoomLevelMax(tileSource.getZoomLevelMax()); + } + binding!!.map.mapView.layerManager.layers.add(layer) + binding!!.map.mapView.setZoomLevelMin(tileSource.zoomLevelMin) + binding!!.map.mapView.setZoomLevelMax(tileSource.zoomLevelMax) } - } - private Marker createBitmapMarker(LatLong latLong, int markerRes) { - var drawable = ContextCompat.getDrawable(this, markerRes); - assert drawable != null; - var bitmap = AndroidGraphicFactory.convertToBitmap(drawable); - return new Marker(latLong, bitmap, -(bitmap.getWidth() / 2), -bitmap.getHeight()); + private fun createBitmapMarker(latLong: LatLong?, markerRes: Int): Marker { + val drawable = ContextCompat.getDrawable(this, markerRes)!! + val bitmap = AndroidGraphicFactory.convertToBitmap(drawable) + return Marker(latLong, bitmap, -(bitmap.width / 2), -bitmap.height) } - private void onLongPress(LatLong tapLatLong) { + private fun onLongPress(tapLatLong: LatLong) { if (missingMarker == null) { // marker to show at the location - var drawable = ContextCompat.getDrawable(this, R.drawable.marker_missing); - assert drawable != null; - var bitmap = AndroidGraphicFactory.convertToBitmap(drawable); - missingMarker = new Marker(tapLatLong, bitmap, -(bitmap.getWidth() / 2), -bitmap.getHeight()) { - @Override - public boolean onTap(LatLong tapLatLong, Point layerXY, Point tapXY) { - SimpleDialogs.confirmOkCancel(MapsActivity.this, R.string.add_missing_station, (dialogInterface, i) -> { - var intent = new Intent(MapsActivity.this, UploadActivity.class); - intent.putExtra(UploadActivity.EXTRA_LATITUDE, getLatLong().latitude); - intent.putExtra(UploadActivity.EXTRA_LONGITUDE, getLatLong().longitude); - startActivity(intent); - }); - return false; + val drawable = ContextCompat.getDrawable(this, R.drawable.marker_missing)!! + val bitmap = AndroidGraphicFactory.convertToBitmap(drawable) + missingMarker = + object : Marker(tapLatLong, bitmap, -(bitmap.width / 2), -bitmap.height) { + override fun onTap(tapLatLong: LatLong, layerXY: Point, tapXY: Point): Boolean { + SimpleDialogs.confirmOkCancel( + this@MapsActivity, + R.string.add_missing_station + ) { dialogInterface: DialogInterface?, i: Int -> + val intent = Intent(this@MapsActivity, UploadActivity::class.java) + intent.putExtra( + UploadActivity.Companion.EXTRA_LATITUDE, + latLong.latitude + ) + intent.putExtra( + UploadActivity.Companion.EXTRA_LONGITUDE, + latLong.longitude + ) + startActivity(intent) + } + return false + } } - }; - binding.map.mapView.getLayerManager().getLayers().add(missingMarker); + binding!!.map.mapView.layerManager.layers.add(missingMarker) } else { - missingMarker.setLatLong(tapLatLong); - missingMarker.requestRedraw(); + missingMarker!!.latLong = tapLatLong + missingMarker!!.requestRedraw() } // feedback for long click - ((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(VibrationEffect.createOneShot(150, VibrationEffect.DEFAULT_AMPLITUDE)); - - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.maps, menu); - - var item = menu.findItem(R.id.menu_toggle_mypos); - myLocSwitch = new CheckBox(this); - myLocSwitch.setButtonDrawable(R.drawable.ic_gps_fix_selector); - myLocSwitch.setChecked(baseApplication.isLocationUpdates()); - item.setActionView(myLocSwitch); - myLocSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { - baseApplication.setLocationUpdates(isChecked); - if (isChecked) { - askedForPermission = false; - registerLocationManager(); - } else { - unregisterLocationManager(); - } - } - ); - - var map = baseApplication.getMap(); - - var osmMapnick = menu.findItem(R.id.osm_mapnik); - osmMapnick.setChecked(map == null); - osmMapnick.setOnMenuItemClickListener(new MapMenuListener(this, baseApplication, null)); + (getSystemService(VIBRATOR_SERVICE) as Vibrator).vibrate( + VibrationEffect.createOneShot( + 150, + VibrationEffect.DEFAULT_AMPLITUDE + ) + ) + } - var mapSubmenu = menu.findItem(R.id.maps_submenu).getSubMenu(); - assert mapSubmenu != null; - for (var tileSource : onlineTileSources.values()) { - var mapItem = mapSubmenu.add(R.id.maps_group, NONE, NONE, tileSource.getName()); - mapItem.setChecked(tileSource.getName().equals(map)); - mapItem.setOnMenuItemClickListener(new MapMenuListener(this, baseApplication, tileSource.getName())); + override fun onCreateOptionsMenu(menu: Menu): Boolean { + super.onCreateOptionsMenu(menu) + menuInflater.inflate(R.menu.maps, menu) + val item = menu.findItem(R.id.menu_toggle_mypos) + myLocSwitch = CheckBox(this) + myLocSwitch!!.setButtonDrawable(R.drawable.ic_gps_fix_selector) + myLocSwitch!!.isChecked = baseApplication!!.isLocationUpdates + item.actionView = myLocSwitch + myLocSwitch!!.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean -> + baseApplication.setLocationUpdates(isChecked) + if (isChecked) { + askedForPermission = false + registerLocationManager() + } else { + unregisterLocationManager() + } } - - var mapDirectory = baseApplication.getMapDirectoryUri(); - if (mapDirectory.isPresent()) { - var documentsTree = getDocumentFileFromTreeUri(mapDirectory.get()); + val map = baseApplication.getMap() + val osmMapnick = menu.findItem(R.id.osm_mapnik) + osmMapnick.isChecked = map == null + osmMapnick.setOnMenuItemClickListener(MapMenuListener(this, baseApplication, null)) + val mapSubmenu = menu.findItem(R.id.maps_submenu).subMenu!! + for (tileSource in onlineTileSources.values) { + val mapItem = mapSubmenu.add(R.id.maps_group, Menu.NONE, Menu.NONE, tileSource.name) + mapItem.isChecked = tileSource.name == map + mapItem.setOnMenuItemClickListener( + MapMenuListener( + this, + baseApplication, + tileSource.name + ) + ) + } + val mapDirectory = baseApplication.getMapDirectoryUri() + if (mapDirectory!!.isPresent) { + val documentsTree = getDocumentFileFromTreeUri(mapDirectory!!.get()) if (documentsTree != null) { - for (var file : documentsTree.listFiles()) { - if (file.isFile() && file.getName().endsWith(".map")) { - var mapItem = mapSubmenu.add(R.id.maps_group, NONE, NONE, file.getName()); - mapItem.setChecked(BaseApplication.toUri(map).map(uri -> file.getUri().equals(uri)).orElse(false)); - mapItem.setOnMenuItemClickListener(new MapMenuListener(this, baseApplication, file.getUri().toString())); + for (file in documentsTree.listFiles()) { + if (file.isFile && file.name!!.endsWith(".map")) { + val mapItem = + mapSubmenu.add(R.id.maps_group, Menu.NONE, Menu.NONE, file.name) + mapItem.isChecked = BaseApplication.Companion.toUri(map).map( + Function { uri: Uri -> file.uri == uri }).orElse(false) + mapItem.setOnMenuItemClickListener( + MapMenuListener( + this, + baseApplication, + file.uri.toString() + ) + ) } } } } - mapSubmenu.setGroupCheckable(R.id.maps_group, true, true); - - var mapFolder = mapSubmenu.add(R.string.map_folder); - mapFolder.setOnMenuItemClickListener(item1 -> { - openMapDirectoryChooser(); - return false; - }); - - var mapTheme = baseApplication.getMapThemeUri(); - var mapThemeDirectory = baseApplication.getMapThemeDirectoryUri(); - - var defaultTheme = menu.findItem(R.id.default_theme); - defaultTheme.setChecked(!mapTheme.isPresent()); - defaultTheme.setOnMenuItemClickListener(new MapThemeMenuListener(this, baseApplication, null)); - var themeSubmenu = menu.findItem(R.id.themes_submenu).getSubMenu(); - assert themeSubmenu != null; - - if (mapThemeDirectory.isPresent()) { - var documentsTree = getDocumentFileFromTreeUri(mapThemeDirectory.get()); + mapSubmenu.setGroupCheckable(R.id.maps_group, true, true) + val mapFolder = mapSubmenu.add(R.string.map_folder) + mapFolder.setOnMenuItemClickListener { item1: MenuItem? -> + openMapDirectoryChooser() + false + } + val mapTheme = baseApplication.getMapThemeUri() + val mapThemeDirectory = baseApplication.getMapThemeDirectoryUri() + val defaultTheme = menu.findItem(R.id.default_theme) + defaultTheme.isChecked = !mapTheme!!.isPresent + defaultTheme.setOnMenuItemClickListener(MapThemeMenuListener(this, baseApplication, null)) + val themeSubmenu = menu.findItem(R.id.themes_submenu).subMenu!! + if (mapThemeDirectory!!.isPresent) { + val documentsTree = getDocumentFileFromTreeUri(mapThemeDirectory!!.get()) if (documentsTree != null) { - for (var file : documentsTree.listFiles()) { - if (file.isFile() && file.getName().endsWith(".xml")) { - var themeName = file.getName(); - var themeItem = themeSubmenu.add(R.id.themes_group, NONE, NONE, themeName); - themeItem.setChecked(mapTheme.map(uri -> file.getUri().equals(uri)).orElse(false)); - themeItem.setOnMenuItemClickListener(new MapThemeMenuListener(this, baseApplication, file.getUri())); - } else if (file.isDirectory()) { - var childFile = file.findFile(file.getName() + ".xml"); + for (file in documentsTree.listFiles()) { + if (file.isFile && file.name!!.endsWith(".xml")) { + val themeName = file.name + val themeItem = + themeSubmenu.add(R.id.themes_group, Menu.NONE, Menu.NONE, themeName) + themeItem.isChecked = mapTheme!!.map { uri: Uri? -> file.uri == uri } + .orElse(false) + themeItem.setOnMenuItemClickListener( + MapThemeMenuListener( + this, + baseApplication, + file.uri + ) + ) + } else if (file.isDirectory) { + val childFile = file.findFile(file.name + ".xml") if (childFile != null) { - var themeName = file.getName(); - var themeItem = themeSubmenu.add(R.id.themes_group, NONE, NONE, themeName); - themeItem.setChecked(mapTheme.map(uri -> childFile.getUri().equals(uri)).orElse(false)); - themeItem.setOnMenuItemClickListener(new MapThemeMenuListener(this, baseApplication, childFile.getUri())); + val themeName = file.name + val themeItem = + themeSubmenu.add(R.id.themes_group, Menu.NONE, Menu.NONE, themeName) + themeItem.isChecked = + mapTheme!!.map { uri: Uri? -> childFile.uri == uri } + .orElse(false) + themeItem.setOnMenuItemClickListener( + MapThemeMenuListener( + this, + baseApplication, + childFile.uri + ) + ) } } } } } - themeSubmenu.setGroupCheckable(R.id.themes_group, true, true); - - var themeFolder = themeSubmenu.add(R.string.theme_folder); - themeFolder.setOnMenuItemClickListener(item12 -> { - openThemeDirectoryChooser(); - return false; - }); - - return true; + themeSubmenu.setGroupCheckable(R.id.themes_group, true, true) + val themeFolder = themeSubmenu.add(R.string.theme_folder) + themeFolder.setOnMenuItemClickListener { item12: MenuItem? -> + openThemeDirectoryChooser() + false + } + return true } - private DocumentFile getDocumentFileFromTreeUri(Uri uri) { + private fun getDocumentFileFromTreeUri(uri: Uri): DocumentFile? { try { - return DocumentFile.fromTreeUri(getApplication(), uri); - } catch (Exception e) { - Log.w(TAG, "Error getting DocumentFile from Uri: " + uri); - } - return null; - } - - protected ActivityResultLauncher themeDirectoryLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { - var uri = result.getData().getData(); - if (uri != null) { - getContentResolver().takePersistableUriPermission( - uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION - ); - baseApplication.setMapThemeDirectoryUri(uri); - recreate(); - } - } - }); - - protected ActivityResultLauncher mapDirectoryLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { - var uri = result.getData().getData(); - if (uri != null) { - getContentResolver().takePersistableUriPermission( - uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION - ); - baseApplication.setMapDirectoryUri(uri); - recreate(); - } - } - }); + return DocumentFile.fromTreeUri(application, uri) + } catch (e: Exception) { + Log.w(TAG, "Error getting DocumentFile from Uri: $uri") + } + return null + } - public void openDirectory(ActivityResultLauncher launcher) { - var intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); - launcher.launch(intent); + protected var themeDirectoryLauncher = registerForActivityResult( + StartActivityForResult() + ) { result: ActivityResult -> + if (result.resultCode == RESULT_OK && result.data != null) { + val uri = result.data!!.data + if (uri != null) { + contentResolver.takePersistableUriPermission( + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + baseApplication!!.setMapThemeDirectoryUri(uri) + recreate() + } + } + } + protected var mapDirectoryLauncher = registerForActivityResult( + StartActivityForResult() + ) { result: ActivityResult -> + if (result.resultCode == RESULT_OK && result.data != null) { + val uri = result.data!!.data + if (uri != null) { + contentResolver.takePersistableUriPermission( + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + baseApplication!!.setMapDirectoryUri(uri) + recreate() + } + } } + fun openDirectory(launcher: ActivityResultLauncher) { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + launcher.launch(intent) + } - private void openMapDirectoryChooser() { - openDirectory(mapDirectoryLauncher); + private fun openMapDirectoryChooser() { + openDirectory(mapDirectoryLauncher) } - private void openThemeDirectoryChooser() { - openDirectory(themeDirectoryLauncher); + private fun openThemeDirectoryChooser() { + openDirectory(themeDirectoryLauncher) } /** * Android Activity life cycle method. */ - @Override - protected void onDestroy() { - binding.map.mapView.destroyAll(); - AndroidGraphicFactory.clearResourceMemoryCache(); - purgeTileCaches(); - super.onDestroy(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.map_info) { - new MapInfoFragment().show(getSupportFragmentManager(), "Map Info Dialog"); + override fun onDestroy() { + binding!!.map.mapView.destroyAll() + AndroidGraphicFactory.clearResourceMemoryCache() + purgeTileCaches() + super.onDestroy() + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.map_info) { + MapInfoFragment().show(supportFragmentManager, "Map Info Dialog") } else { - return super.onOptionsItemSelected(item); + return super.onOptionsItemSelected(item) } - return true; + return true } - private void reloadMap() { - destroyClusterManager(); - new LoadMapMarkerTask(this).start(); + private fun reloadMap() { + destroyClusterManager() + LoadMapMarkerTask(this).start() } - private void runUpdateCountriesAndStations() { - binding.map.progressBar.setVisibility(View.VISIBLE); - baseApplication.getRsapiClient().runUpdateCountriesAndStations(this, baseApplication, success -> reloadMap()); + private fun runUpdateCountriesAndStations() { + binding!!.map.progressBar.visibility = View.VISIBLE + baseApplication.getRsapiClient().runUpdateCountriesAndStations( + this, + baseApplication + ) { success: Boolean -> reloadMap() } } - - private void onStationsLoaded(List stationList, List uploadList) { + private fun onStationsLoaded(stationList: List?, uploadList: List?) { try { - createClusterManager(); - addMarkers(stationList, uploadList); - binding.map.progressBar.setVisibility(View.GONE); - Toast.makeText(this, getResources().getQuantityString(R.plurals.stations_loaded, stationList.size(), stationList.size()), Toast.LENGTH_LONG).show(); - } catch (Exception e) { - Log.e(TAG, "Error loading markers", e); + createClusterManager() + addMarkers(stationList, uploadList) + binding!!.map.progressBar.visibility = View.GONE + Toast.makeText( + this, + resources.getQuantityString( + R.plurals.stations_loaded, + stationList!!.size, + stationList.size + ), + Toast.LENGTH_LONG + ).show() + } catch (e: Exception) { + Log.e(TAG, "Error loading markers", e) } } - @Override - public void onTap(BahnhofGeoItem marker) { - var intent = new Intent(MapsActivity.this, DetailsActivity.class); - var id = marker.getStation().getId(); - var country = marker.getStation().getCountry(); + override fun onTap(marker: BahnhofGeoItem) { + val intent = Intent(this@MapsActivity, DetailsActivity::class.java) + val id = marker.station!!.id + val country = marker.station!!.country try { - var station = dbAdapter.getStationByKey(country, id); - intent.putExtra(DetailsActivity.EXTRA_STATION, station); - startActivity(intent); - } catch (RuntimeException e) { - Log.wtf(TAG, String.format("Could not fetch station id %s that we put onto the map", id), e); + val station = dbAdapter!!.getStationByKey(country, id) + intent.putExtra(DetailsActivity.Companion.EXTRA_STATION, station) + startActivity(intent) + } catch (e: RuntimeException) { + Log.wtf( + TAG, + String.format("Could not fetch station id %s that we put onto the map", id), + e + ) } } - public void setMyLocSwitch(boolean checked) { + fun setMyLocSwitch(checked: Boolean) { if (myLocSwitch != null) { - myLocSwitch.setChecked(checked); + myLocSwitch!!.isChecked = checked } - baseApplication.setLocationUpdates(checked); + baseApplication.setLocationUpdates(checked) } - @Override - public void stationFilterChanged(StationFilter stationFilter) { - reloadMap(); + override fun stationFilterChanged(stationFilter: StationFilter?) { + reloadMap() } - @Override - public void sortOrderChanged(boolean sortByDistance) { + override fun sortOrderChanged(sortByDistance: Boolean) { // unused } - private static class LoadMapMarkerTask extends Thread { - private final WeakReference activityRef; + private class LoadMapMarkerTask(activity: MapsActivity) : Thread() { + private val activityRef: WeakReference - public LoadMapMarkerTask(MapsActivity activity) { - this.activityRef = new WeakReference<>(activity); + init { + activityRef = WeakReference(activity) } - @Override - public void run() { - var stationList = activityRef.get().readStations(); - var uploadList = activityRef.get().readPendingUploads(); - var mapsActivity = activityRef.get(); - if (mapsActivity != null) { - mapsActivity.runOnUiThread(() -> mapsActivity.onStationsLoaded(stationList, uploadList)); - } + override fun run() { + val stationList = activityRef.get()!!.readStations() + val uploadList = activityRef.get()!!.readPendingUploads() + val mapsActivity = activityRef.get() + mapsActivity?.runOnUiThread { mapsActivity.onStationsLoaded(stationList, uploadList) } } - } - private List readStations() { + private fun readStations(): List? { try { - return dbAdapter.getAllStations(baseApplication.getStationFilter(), baseApplication.getCountryCodes()); - } catch (Exception e) { - Log.i(TAG, "Datenbank konnte nicht geöffnet werden"); + return dbAdapter!!.getAllStations( + baseApplication.getStationFilter(), + baseApplication.getCountryCodes() + ) + } catch (e: Exception) { + Log.i(TAG, "Datenbank konnte nicht geöffnet werden") } - return null; + return null } - private List readPendingUploads() { + private fun readPendingUploads(): List? { try { - return dbAdapter.getPendingUploads(false); - } catch (Exception e) { - Log.i(TAG, "Datenbank konnte nicht geöffnet werden"); + return dbAdapter!!.getPendingUploads(false) + } catch (e: Exception) { + Log.i(TAG, "Datenbank konnte nicht geöffnet werden") } - return null; + return null } - private List createMarkerBitmaps() { - var markerBitmaps = new ArrayList(); - markerBitmaps.add(createSmallSingleIconMarker()); - markerBitmaps.add(createSmallClusterIconMarker()); - markerBitmaps.add(createLargeClusterIconMarker()); - return markerBitmaps; + private fun createMarkerBitmaps(): List { + val markerBitmaps = ArrayList() + markerBitmaps.add(createSmallSingleIconMarker()) + markerBitmaps.add(createSmallClusterIconMarker()) + markerBitmaps.add(createLargeClusterIconMarker()) + return markerBitmaps } /** * large cluster icon. 100 will be ignored. */ - private MarkerBitmap createLargeClusterIconMarker() { - var bitmapBalloonMN = loadBitmap(R.drawable.balloon_m_n); - var paint = AndroidGraphicFactory.INSTANCE.createPaint(); - paint.setStyle(Style.FILL); - paint.setTextAlign(Align.CENTER); - paint.setTypeface(FontFamily.DEFAULT, FontStyle.BOLD); - paint.setColor(Color.BLACK); - return new MarkerBitmap(this.getApplicationContext(), bitmapBalloonMN, - new Point(0, 0), 11f, 100, paint); + private fun createLargeClusterIconMarker(): MarkerBitmap { + val bitmapBalloonMN = loadBitmap(R.drawable.balloon_m_n) + val paint = AndroidGraphicFactory.INSTANCE.createPaint() + paint.setStyle(Style.FILL) + paint.setTextAlign(Align.CENTER) + paint.setTypeface(FontFamily.DEFAULT, FontStyle.BOLD) + paint.color = Color.BLACK + return MarkerBitmap( + this.applicationContext, bitmapBalloonMN, + 11f, paint + ) } /** * small cluster icon. for 10 or less items. */ - private MarkerBitmap createSmallClusterIconMarker() { - var bitmapBalloonSN = loadBitmap(R.drawable.balloon_s_n); - var paint = AndroidGraphicFactory.INSTANCE.createPaint(); - paint.setStyle(Style.FILL); - paint.setTextAlign(Align.CENTER); - paint.setTypeface(FontFamily.DEFAULT, FontStyle.BOLD); - paint.setColor(Color.BLACK); - return new MarkerBitmap(this.getApplicationContext(), bitmapBalloonSN, - new Point(0, 0), 9f, 10, paint); - } - - private MarkerBitmap createSmallSingleIconMarker() { - var bitmapWithPhoto = loadBitmap(R.drawable.marker_green); - var markerWithoutPhoto = loadBitmap(R.drawable.marker_red); - var markerOwnPhoto = loadBitmap(R.drawable.marker_violet); - var markerPendingUpload = loadBitmap(R.drawable.marker_yellow); - - var markerWithPhotoInactive = loadBitmap(R.drawable.marker_green_inactive); - var markerWithoutPhotoInactive = loadBitmap(R.drawable.marker_red_inactive); - var markerOwnPhotoInactive = loadBitmap(R.drawable.marker_violet_inactive); - - var paint = AndroidGraphicFactory.INSTANCE.createPaint(); - paint.setStyle(Style.FILL); - paint.setTextAlign(Align.CENTER); - paint.setTypeface(FontFamily.DEFAULT, FontStyle.BOLD); - paint.setColor(Color.RED); - - return new MarkerBitmap(this.getApplicationContext(), markerWithoutPhoto, bitmapWithPhoto, markerOwnPhoto, - markerWithoutPhotoInactive, markerWithPhotoInactive, markerOwnPhotoInactive, markerPendingUpload, - new Point(0, -(markerWithoutPhoto.getHeight() / 2.0)), 10f, 1, paint); - } - - private Bitmap loadBitmap(int resourceId) { - var bitmap = AndroidGraphicFactory.convertToBitmap(ResourcesCompat.getDrawable(getResources(), resourceId, null)); - bitmap.incrementRefCount(); - return bitmap; - } - - private void addMarkers(List stationMarker, List uploadList) { - double minLat = 0; - double maxLat = 0; - double minLon = 0; - double maxLon = 0; - for (var station : stationMarker) { - var isPendingUpload = isPendingUpload(station, uploadList); - var geoItem = new BahnhofGeoItem(station, isPendingUpload); - var bahnhofPos = geoItem.getLatLong(); + private fun createSmallClusterIconMarker(): MarkerBitmap { + val bitmapBalloonSN = loadBitmap(R.drawable.balloon_s_n) + val paint = AndroidGraphicFactory.INSTANCE.createPaint() + paint.setStyle(Style.FILL) + paint.setTextAlign(Align.CENTER) + paint.setTypeface(FontFamily.DEFAULT, FontStyle.BOLD) + paint.color = Color.BLACK + return MarkerBitmap( + this.applicationContext, bitmapBalloonSN, + 9f, paint + ) + } + + private fun createSmallSingleIconMarker(): MarkerBitmap { + val bitmapWithPhoto = loadBitmap(R.drawable.marker_green) + val markerWithoutPhoto = loadBitmap(R.drawable.marker_red) + val markerOwnPhoto = loadBitmap(R.drawable.marker_violet) + val markerPendingUpload = loadBitmap(R.drawable.marker_yellow) + val markerWithPhotoInactive = loadBitmap(R.drawable.marker_green_inactive) + val markerWithoutPhotoInactive = loadBitmap(R.drawable.marker_red_inactive) + val markerOwnPhotoInactive = loadBitmap(R.drawable.marker_violet_inactive) + val paint = AndroidGraphicFactory.INSTANCE.createPaint() + paint.setStyle(Style.FILL) + paint.setTextAlign(Align.CENTER) + paint.setTypeface(FontFamily.DEFAULT, FontStyle.BOLD) + paint.color = Color.RED + return MarkerBitmap( + this.applicationContext, + markerWithoutPhoto, + bitmapWithPhoto, + markerOwnPhoto, + markerWithoutPhotoInactive, + markerWithPhotoInactive, + markerOwnPhotoInactive, + markerPendingUpload, + 10f, + paint + ) + } + + private fun loadBitmap(resourceId: Int): Bitmap { + val bitmap = AndroidGraphicFactory.convertToBitmap( + ResourcesCompat.getDrawable( + resources, + resourceId, + null + ) + ) + bitmap.incrementRefCount() + return bitmap + } + + private fun addMarkers(stationMarker: List?, uploadList: List?) { + var minLat = 0.0 + var maxLat = 0.0 + var minLon = 0.0 + var maxLon = 0.0 + for (station in stationMarker!!) { + val isPendingUpload = isPendingUpload(station, uploadList) + val geoItem = BahnhofGeoItem(station, isPendingUpload) + val bahnhofPos = geoItem.getLatLong() if (minLat == 0.0) { - minLat = bahnhofPos.latitude; - maxLat = bahnhofPos.latitude; - minLon = bahnhofPos.longitude; - maxLon = bahnhofPos.longitude; + minLat = bahnhofPos!!.latitude + maxLat = bahnhofPos.latitude + minLon = bahnhofPos.longitude + maxLon = bahnhofPos.longitude } else { - minLat = Math.min(minLat, bahnhofPos.latitude); - maxLat = Math.max(maxLat, bahnhofPos.latitude); - minLon = Math.min(minLon, bahnhofPos.longitude); - maxLon = Math.max(maxLon, bahnhofPos.longitude); + minLat = Math.min(minLat, bahnhofPos!!.latitude) + maxLat = Math.max(maxLat, bahnhofPos.latitude) + minLon = Math.min(minLon, bahnhofPos.longitude) + maxLon = Math.max(maxLon, bahnhofPos.longitude) } - clusterer.addItem(geoItem); + clusterer!!.addItem(geoItem) } - - clusterer.redraw(); - - if (myPos == null || (myPos.latitude == 0.0 && myPos.longitude == 0.0)) { - myPos = new LatLong((minLat + maxLat) / 2, (minLon + maxLon) / 2); + clusterer!!.redraw() + if (myPos == null || myPos!!.latitude == 0.0 && myPos!!.longitude == 0.0) { + myPos = LatLong((minLat + maxLat) / 2, (minLon + maxLon) / 2) } - updatePosition(); + updatePosition() } - private boolean isPendingUpload(Station station, List uploadList) { - for (var upload : uploadList) { - if (upload.isPendingPhotoUpload() && station.getId().equals(upload.getStationId()) && station.getCountry().equals(upload.getCountry())) { - return true; + private fun isPendingUpload(station: Station?, uploadList: List?): Boolean { + for (upload in uploadList!!) { + if (upload!!.isPendingPhotoUpload && station!!.id == upload.stationId && station.country == upload.country) { + return true } } - return false; + return false } - @Override - public void onResume() { - super.onResume(); - if (this.layer instanceof TileDownloadLayer) { - ((TileDownloadLayer) this.layer).onResume(); + public override fun onResume() { + super.onResume() + if (layer is TileDownloadLayer) { + (layer as TileDownloadLayer?)!!.onResume() } - if (baseApplication.getLastUpdate() == 0) { - runUpdateCountriesAndStations(); + if (baseApplication.getLastUpdate() == 0L) { + runUpdateCountriesAndStations() } else { - reloadMap(); + reloadMap() } - if (baseApplication.isLocationUpdates()) { - registerLocationManager(); + if (baseApplication!!.isLocationUpdates) { + registerLocationManager() } - binding.map.stationFilterBar.init(baseApplication, this); - binding.map.stationFilterBar.setSortOrderEnabled(false); + binding!!.map.stationFilterBar.init(baseApplication, this) + binding!!.map.stationFilterBar.setSortOrderEnabled(false) } - private void createClusterManager() { + private fun createClusterManager() { // create clusterer instance - clusterer = new ClusterManager<>( - binding.map.mapView, - createMarkerBitmaps(), - (byte) 9, - this); + clusterer = ClusterManager( + binding!!.map.mapView, + createMarkerBitmaps(), 9.toByte(), + this + ) // this uses the framebuffer position, the mapview position can be out of sync with // what the user sees on the screen if an animation is in progress - this.binding.map.mapView.getModel().frameBufferModel.addObserver(clusterer); + binding!!.map.mapView.model.frameBufferModel.addObserver(clusterer) } - @Override - protected void onPause() { - if (this.layer instanceof TileDownloadLayer) { - ((TileDownloadLayer) this.layer).onPause(); + override fun onPause() { + if (layer is TileDownloadLayer) { + (layer as TileDownloadLayer?)!!.onPause() } - unregisterLocationManager(); - var mapPosition = binding.map.mapView.getModel().mapViewPosition.getMapPosition(); - baseApplication.setLastMapPosition(mapPosition); - destroyClusterManager(); - super.onPause(); + unregisterLocationManager() + val mapPosition = binding!!.map.mapView.model.mapViewPosition.mapPosition + baseApplication.setLastMapPosition(mapPosition) + destroyClusterManager() + super.onPause() } - private void destroyClusterManager() { + private fun destroyClusterManager() { if (clusterer != null) { - clusterer.destroyGeoClusterer(); - this.binding.map.mapView.getModel().frameBufferModel.removeObserver(clusterer); - clusterer = null; + clusterer!!.destroyGeoClusterer() + binding!!.map.mapView.model.frameBufferModel.removeObserver(clusterer) + clusterer = null } } - @Override - public void onLocationChanged(Location location) { - myPos = new LatLong(location.getLatitude(), location.getLongitude()); - updatePosition(); - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - - } - - @Override - public void onProviderEnabled(@NonNull String provider) { - + override fun onLocationChanged(location: Location) { + myPos = LatLong(location.latitude, location.longitude) + updatePosition() } - @Override - public void onProviderDisabled(@NonNull String provider) { - - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); + override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {} + override fun onProviderEnabled(provider: String) {} + override fun onProviderDisabled(provider: String) {} + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_FINE_LOCATION) { - Log.i(TAG, "Received response for location permission request."); + Log.i(TAG, "Received response for location permission request.") // Check if the required permission has been granted - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Location permission has been granted - registerLocationManager(); + registerLocationManager() } else { //Permission not granted - Toast.makeText(MapsActivity.this, R.string.grant_location_permission, Toast.LENGTH_LONG).show(); + Toast.makeText( + this@MapsActivity, + R.string.grant_location_permission, + Toast.LENGTH_LONG + ).show() } } } - public void registerLocationManager() { - + fun registerLocationManager() { try { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + if (ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_COARSE_LOCATION + ) != PackageManager.PERMISSION_GRANTED + ) { if (!askedForPermission) { - ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_FINE_LOCATION); - askedForPermission = true; + ActivityCompat.requestPermissions( + this, + arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), + REQUEST_FINE_LOCATION + ) + askedForPermission = true } - setMyLocSwitch(false); - return; + setMyLocSwitch(false) + return } - - locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE); + locationManager = + applicationContext.getSystemService(LOCATION_SERVICE) as LocationManager // getting GPS status - var isGPSEnabled = locationManager - .isProviderEnabled(LocationManager.GPS_PROVIDER); + val isGPSEnabled = locationManager + .isProviderEnabled(LocationManager.GPS_PROVIDER) // if GPS Enabled get lat/long using GPS Services if (isGPSEnabled) { - locationManager.requestLocationUpdates( - LocationManager.GPS_PROVIDER, - MIN_TIME_BW_UPDATES, - MIN_DISTANCE_CHANGE_FOR_UPDATES, this); - Log.d(TAG, "GPS Enabled"); + locationManager!!.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + MIN_TIME_BW_UPDATES, + MIN_DISTANCE_CHANGE_FOR_UPDATES.toFloat(), this + ) + Log.d(TAG, "GPS Enabled") if (locationManager != null) { - var loc = locationManager - .getLastKnownLocation(LocationManager.GPS_PROVIDER); - myPos = new LatLong(loc.getLatitude(), loc.getLongitude()); + val loc = locationManager!! + .getLastKnownLocation(LocationManager.GPS_PROVIDER) + myPos = LatLong(loc!!.latitude, loc.longitude) } } else { // getting network status - var isNetworkEnabled = locationManager - .isProviderEnabled(LocationManager.NETWORK_PROVIDER); + val isNetworkEnabled = locationManager + .isProviderEnabled(LocationManager.NETWORK_PROVIDER) // First get location from Network Provider if (isNetworkEnabled) { - locationManager.requestLocationUpdates( - LocationManager.NETWORK_PROVIDER, - MIN_TIME_BW_UPDATES, - MIN_DISTANCE_CHANGE_FOR_UPDATES, this); - Log.d(TAG, "Network Location enabled"); + locationManager!!.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, + MIN_TIME_BW_UPDATES, + MIN_DISTANCE_CHANGE_FOR_UPDATES.toFloat(), this + ) + Log.d(TAG, "Network Location enabled") if (locationManager != null) { - var loc = locationManager - .getLastKnownLocation(LocationManager.NETWORK_PROVIDER); - myPos = new LatLong(loc.getLatitude(), loc.getLongitude()); + val loc = locationManager!! + .getLastKnownLocation(LocationManager.NETWORK_PROVIDER) + myPos = LatLong(loc!!.latitude, loc.longitude) } } } - setMyLocSwitch(true); - } catch (Exception e) { - Log.e(TAG, "Error registering LocationManager", e); - var b = new Bundle(); - b.putString("error", "Error registering LocationManager: " + e); - locationManager = null; - myPos = null; - setMyLocSwitch(false); - return; + setMyLocSwitch(true) + } catch (e: Exception) { + Log.e(TAG, "Error registering LocationManager", e) + val b = Bundle() + b.putString("error", "Error registering LocationManager: $e") + locationManager = null + myPos = null + setMyLocSwitch(false) + return } - Log.i(TAG, "LocationManager registered"); - updatePosition(); + Log.i(TAG, "LocationManager registered") + updatePosition() } - private void unregisterLocationManager() { + private fun unregisterLocationManager() { if (locationManager != null) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { - locationManager.removeUpdates(this); + if (ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + locationManager!!.removeUpdates(this) } - locationManager = null; + locationManager = null } - Log.i(TAG, "LocationManager unregistered"); + Log.i(TAG, "LocationManager unregistered") } - private void updatePosition() { - if (myLocSwitch != null && myLocSwitch.isChecked()) { - binding.map.mapView.setCenter(myPos); - binding.map.mapView.repaint(); + private fun updatePosition() { + if (myLocSwitch != null && myLocSwitch!!.isChecked) { + binding!!.map.mapView.setCenter(myPos) + binding!!.map.mapView.repaint() } } - protected class BahnhofGeoItem implements GeoItem { - public Station station; - public final LatLong latLong; - public final boolean pendingUpload; - - public BahnhofGeoItem(Station station, boolean pendingUpload) { - this.station = station; - this.pendingUpload = pendingUpload; - this.latLong = new LatLong(station.getLat(), station.getLon()); - } + protected inner class BahnhofGeoItem( + var station: Station?, + override val isPendingUpload: Boolean + ) : GeoItem { + override val latLong: LatLong - public LatLong getLatLong() { - return latLong; + init { + latLong = LatLong(station!!.lat, station!!.lon) } - @Override - public String getTitle() { - return station.getTitle(); + override fun getLatLong(): LatLong? { + return latLong } - @Override - public boolean hasPhoto() { - return station.hasPhoto(); - } - - @Override - public boolean ownPhoto() { - return hasPhoto() && station.getPhotographer().equals(nickname); - } + override val title: String? + get() = station!!.title - @Override - public boolean stationActive() { - return station.getActive(); + override fun hasPhoto(): Boolean { + return station!!.hasPhoto() } - @Override - public boolean isPendingUpload() { - return pendingUpload; + override fun ownPhoto(): Boolean { + return hasPhoto() && station!!.photographer == nickname } - public Station getStation() { - return station; + override fun stationActive(): Boolean { + return station!!.active } - } - private static class MapMenuListener implements MenuItem.OnMenuItemClickListener { - - private final WeakReference mapsActivityRef; - - private final BaseApplication baseApplication; - - private final String map; + private class MapMenuListener( + mapsActivity: MapsActivity, + private val baseApplication: BaseApplication?, + private val map: String? + ) : MenuItem.OnMenuItemClickListener { + private val mapsActivityRef: WeakReference - private MapMenuListener(MapsActivity mapsActivity, BaseApplication baseApplication, String map) { - this.mapsActivityRef = new WeakReference<>(mapsActivity); - this.baseApplication = baseApplication; - this.map = map; + init { + mapsActivityRef = WeakReference(mapsActivity) } - @Override - public boolean onMenuItemClick(MenuItem item) { - item.setChecked(true); - if (item.getItemId() == R.id.osm_mapnik) { // default Mapnik online tiles - baseApplication.setMap(null); + override fun onMenuItemClick(item: MenuItem): Boolean { + item.isChecked = true + if (item.itemId == R.id.osm_mapnik) { // default Mapnik online tiles + baseApplication.setMap(null) } else { - baseApplication.setMap(map); + baseApplication.setMap(map) } - - var mapsActivity = mapsActivityRef.get(); - if (mapsActivity != null) { - mapsActivity.recreate(); - } - return false; + val mapsActivity = mapsActivityRef.get() + mapsActivity?.recreate() + return false } } - private static class MapThemeMenuListener implements MenuItem.OnMenuItemClickListener { - - private final WeakReference mapsActivityRef; + private class MapThemeMenuListener( + mapsActivity: MapsActivity, + private val baseApplication: BaseApplication?, + private val mapThemeUri: Uri? + ) : MenuItem.OnMenuItemClickListener { + private val mapsActivityRef: WeakReference - private final BaseApplication baseApplication; - - private final Uri mapThemeUri; - - private MapThemeMenuListener(MapsActivity mapsActivity, BaseApplication baseApplication, Uri mapThemeUri) { - this.mapsActivityRef = new WeakReference<>(mapsActivity); - this.baseApplication = baseApplication; - this.mapThemeUri = mapThemeUri; + init { + mapsActivityRef = WeakReference(mapsActivity) } - @Override - public boolean onMenuItemClick(MenuItem item) { - item.setChecked(true); - if (item.getItemId() == R.id.default_theme) { // default theme - baseApplication.setMapThemeUri(null); + override fun onMenuItemClick(item: MenuItem): Boolean { + item.isChecked = true + if (item.itemId == R.id.default_theme) { // default theme + baseApplication!!.setMapThemeUri(null) } else { - baseApplication.setMapThemeUri(mapThemeUri); - } - - var mapsActivity = mapsActivityRef.get(); - if (mapsActivity != null) { - mapsActivity.recreate(); + baseApplication!!.setMapThemeUri(mapThemeUri) } - return false; + val mapsActivity = mapsActivityRef.get() + mapsActivity?.recreate() + return false } } -} + companion object { + const val EXTRAS_LATITUDE = "Extras_Latitude" + const val EXTRAS_LONGITUDE = "Extras_Longitude" + const val EXTRAS_MARKER = "Extras_Marker" + + // The minimum distance to change Updates in meters + private const val MIN_DISTANCE_CHANGE_FOR_UPDATES: Long = 1 // meters + // The minimum time between updates in milliseconds + private const val MIN_TIME_BW_UPDATES: Long = 500 // minute + private val TAG = MapsActivity::class.java.simpleName + private const val REQUEST_FINE_LOCATION = 1 + private const val USER_AGENT = "railway-stations.org-android" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/MyDataActivity.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/MyDataActivity.kt index bcb89ae4..e014961b 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/MyDataActivity.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/MyDataActivity.kt @@ -1,452 +1,481 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; - -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.view.ContextThemeWrapper; -import android.view.View; -import android.widget.EditText; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; - -import org.apache.commons.lang3.StringUtils; - -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.security.NoSuchAlgorithmException; -import java.util.Objects; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityMydataBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ChangePasswordBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.SimpleDialogs; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.License; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Profile; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Token; -import de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi.RSAPIClient; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class MyDataActivity extends AppCompatActivity { - - private static final String TAG = MyDataActivity.class.getSimpleName(); - - private License license; - private BaseApplication baseApplication; - private RSAPIClient rsapiClient; - private Profile profile; - private ActivityMydataBinding binding; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ActivityMydataBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle(R.string.login); - - binding.myData.profileForm.setVisibility(View.INVISIBLE); - - baseApplication = (BaseApplication) getApplication(); - rsapiClient = baseApplication.getRsapiClient(); - - setProfileToUI(baseApplication.getProfile()); - - oauthAuthorizationCallback(getIntent()); - if (isLoginDataAvailable()) { - loadRemoteProfile(); +package de.bahnhoefe.deutschlands.bahnhofsfotos + +import android.content.DialogInterface +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.util.Patterns +import android.view.ContextThemeWrapper +import android.view.View +import android.widget.EditText +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityMydataBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ChangePasswordBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.SimpleDialogs +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.License +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Profile +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Token +import de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi.RSAPIClient +import org.apache.commons.lang3.StringUtils +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.io.UnsupportedEncodingException +import java.net.MalformedURLException +import java.net.URL +import java.net.URLEncoder +import java.nio.charset.StandardCharsets +import java.security.NoSuchAlgorithmException +import java.util.Objects + +class MyDataActivity : AppCompatActivity() { + private var license: License? = null + private var baseApplication: BaseApplication? = null + private var rsapiClient: RSAPIClient? = null + private var profile: Profile? = null + private var binding: ActivityMydataBinding? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMydataBinding.inflate( + layoutInflater + ) + setContentView(binding!!.root) + Objects.requireNonNull(supportActionBar).setDisplayHomeAsUpEnabled(true) + supportActionBar!!.setTitle(R.string.login) + binding!!.myData.profileForm.visibility = View.INVISIBLE + baseApplication = application as BaseApplication + rsapiClient = baseApplication.getRsapiClient() + setProfileToUI(baseApplication.getProfile()) + oauthAuthorizationCallback(intent) + if (isLoginDataAvailable) { + loadRemoteProfile() } } - private void setProfileToUI(Profile profile) { - binding.myData.etNickname.setText(profile.getNickname()); - binding.myData.etEmail.setText(profile.getEmail()); - binding.myData.etLinking.setText(profile.getLink()); - license = profile.getLicense(); - binding.myData.cbLicenseCC0.setChecked(license == License.CC0); - binding.myData.cbOwnPhoto.setChecked(profile.getPhotoOwner()); - binding.myData.cbAnonymous.setChecked(profile.getAnonymous()); - onAnonymousChecked(null); - - if (profile.getEmailVerified()) { - binding.myData.tvEmailVerification.setText(R.string.emailVerified); - binding.myData.tvEmailVerification.setTextColor(getResources().getColor(R.color.emailVerified, null)); + private fun setProfileToUI(profile: Profile?) { + binding!!.myData.etNickname.setText(profile!!.nickname) + binding!!.myData.etEmail.setText(profile.email) + binding!!.myData.etLinking.setText(profile.link) + license = profile.license + binding!!.myData.cbLicenseCC0.isChecked = license === License.CC0 + binding!!.myData.cbOwnPhoto.isChecked = profile.photoOwner + binding!!.myData.cbAnonymous.isChecked = profile.anonymous + onAnonymousChecked(null) + if (profile.emailVerified) { + binding!!.myData.tvEmailVerification.setText(R.string.emailVerified) + binding!!.myData.tvEmailVerification.setTextColor( + resources.getColor( + R.color.emailVerified, + null + ) + ) } else { - binding.myData.tvEmailVerification.setText(R.string.emailUnverified); - binding.myData.tvEmailVerification.setTextColor(getResources().getColor(R.color.emailUnverified, null)); + binding!!.myData.tvEmailVerification.setText(R.string.emailUnverified) + binding!!.myData.tvEmailVerification.setTextColor( + resources.getColor( + R.color.emailUnverified, + null + ) + ) } - this.profile = profile; + this.profile = profile } - private void loadRemoteProfile() { - binding.myData.loginForm.setVisibility(View.VISIBLE); - binding.myData.profileForm.setVisibility(View.GONE); - binding.myData.progressBar.setVisibility(View.VISIBLE); - - rsapiClient.getProfile().enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - binding.myData.progressBar.setVisibility(View.GONE); - - switch (response.code()) { - case 200: - Log.i(TAG, "Successfully loaded profile"); - var remoteProfile = response.body(); + private fun loadRemoteProfile() { + binding!!.myData.loginForm.visibility = View.VISIBLE + binding!!.myData.profileForm.visibility = View.GONE + binding!!.myData.progressBar.visibility = View.VISIBLE + rsapiClient.getProfile().enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + binding!!.myData.progressBar.visibility = View.GONE + when (response.code()) { + 200 -> { + Log.i(TAG, "Successfully loaded profile") + val remoteProfile = response.body() if (remoteProfile != null) { - saveLocalProfile(remoteProfile); - showProfileView(); + saveLocalProfile(remoteProfile) + showProfileView() } - break; - case 401: - logout(null); - SimpleDialogs.confirmOk(MyDataActivity.this, R.string.authorization_failed); - break; - default: - SimpleDialogs.confirmOk(MyDataActivity.this, - getString(R.string.read_profile_failed, String.valueOf(response.code()))); + } + + 401 -> { + logout(null) + confirmOk(this@MyDataActivity, R.string.authorization_failed) + } + + else -> confirmOk( + this@MyDataActivity, + getString(R.string.read_profile_failed, response.code().toString()) + ) } } - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - binding.myData.progressBar.setVisibility(View.GONE); - SimpleDialogs.confirmOk(MyDataActivity.this, - getString(R.string.read_profile_failed, t.getMessage())); + override fun onFailure(call: Call, t: Throwable) { + binding!!.myData.progressBar.visibility = View.GONE + confirmOk( + this@MyDataActivity, + getString(R.string.read_profile_failed, t.message) + ) } - }); + }) } - private void showProfileView() { - binding.myData.loginForm.setVisibility(View.GONE); - binding.myData.profileForm.setVisibility(View.VISIBLE); - Objects.requireNonNull(getSupportActionBar()).setTitle(R.string.tvProfile); - binding.myData.btProfileSave.setText(R.string.bt_mydata_commit); - binding.myData.btLogout.setVisibility(View.VISIBLE); - binding.myData.btChangePassword.setVisibility(View.VISIBLE); + private fun showProfileView() { + binding!!.myData.loginForm.visibility = View.GONE + binding!!.myData.profileForm.visibility = View.VISIBLE + Objects.requireNonNull(supportActionBar).setTitle(R.string.tvProfile) + binding!!.myData.btProfileSave.setText(R.string.bt_mydata_commit) + binding!!.myData.btLogout.visibility = View.VISIBLE + binding!!.myData.btChangePassword.visibility = View.VISIBLE } - private void oauthAuthorizationCallback(Intent intent) { - if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) { - var data = intent.getData(); + private fun oauthAuthorizationCallback(intent: Intent?) { + if (intent != null && Intent.ACTION_VIEW == intent.action) { + val data = intent.data if (data != null) { - if (data.toString().startsWith(baseApplication.getRsapiClient().getRedirectUri())) { - var code = data.getQueryParameter("code"); + if (data.toString().startsWith(baseApplication.getRsapiClient().redirectUri)) { + val code = data.getQueryParameter("code") if (code != null) { - baseApplication.getRsapiClient().requestAccessToken(code).enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - var token = response.body(); - Log.d(TAG, String.valueOf(token)); - if (token != null) { - baseApplication.setAccessToken(token.getAccessToken()); - baseApplication.getRsapiClient().setToken(token); - loadRemoteProfile(); - } else { - Toast.makeText(MyDataActivity.this, getString(R.string.authorization_error, "no token"), Toast.LENGTH_LONG).show(); + baseApplication.getRsapiClient().requestAccessToken(code) + .enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + val token = response.body() + Log.d(TAG, token.toString()) + if (token != null) { + baseApplication.setAccessToken(token.accessToken) + baseApplication.getRsapiClient().setToken(token) + loadRemoteProfile() + } else { + Toast.makeText( + this@MyDataActivity, + getString(R.string.authorization_error, "no token"), + Toast.LENGTH_LONG + ).show() + } } - } - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Toast.makeText(MyDataActivity.this, getString(R.string.authorization_error, t.getMessage()), Toast.LENGTH_LONG).show(); - } - }); + override fun onFailure(call: Call, t: Throwable) { + Toast.makeText( + this@MyDataActivity, + getString(R.string.authorization_error, t.message), + Toast.LENGTH_LONG + ).show() + } + }) } else { - String error = data.getQueryParameter("error"); + val error = data.getQueryParameter("error") if (error != null) { - Toast.makeText(this, getString(R.string.authorization_error, error), Toast.LENGTH_LONG).show(); + Toast.makeText( + this, + getString(R.string.authorization_error, error), + Toast.LENGTH_LONG + ).show() } } - - if (isLoginDataAvailable()) { - loadRemoteProfile(); + if (isLoginDataAvailable) { + loadRemoteProfile() } } } } } - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - oauthAuthorizationCallback(intent); + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + oauthAuthorizationCallback(intent) } - public void selectLicense(View view) { - license = binding.myData.cbLicenseCC0.isChecked() ? License.CC0 : License.UNKNOWN; - if (license != License.CC0) { - SimpleDialogs.confirmOk(this, R.string.cc0_needed); + fun selectLicense(view: View?) { + license = if (binding!!.myData.cbLicenseCC0.isChecked) License.CC0 else License.UNKNOWN + if (license !== License.CC0) { + confirmOk(this, R.string.cc0_needed) } } - public void save(View view) { - profile = createProfileFromUI(); + fun save(view: View?) { + profile = createProfileFromUI() if (!isValid(profile)) { - return; + return } - if (rsapiClient.hasToken()) { - binding.myData.progressBar.setVisibility(View.VISIBLE); - - rsapiClient.saveProfile(profile).enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - binding.myData.progressBar.setVisibility(View.GONE); - - switch (response.code()) { - case 200: - Log.i(TAG, "Successfully saved profile"); - break; - case 202: - SimpleDialogs.confirmOk(MyDataActivity.this, R.string.password_email); - break; - case 400: - SimpleDialogs.confirmOk(MyDataActivity.this, R.string.profile_wrong_data); - break; - case 401: - logout(view); - SimpleDialogs.confirmOk(MyDataActivity.this, R.string.authorization_failed); - break; - case 409: - SimpleDialogs.confirmOk(MyDataActivity.this, R.string.profile_conflict); - break; - default: - SimpleDialogs.confirmOk(MyDataActivity.this, - getString(R.string.save_profile_failed, String.valueOf(response.code()))); + if (rsapiClient!!.hasToken()) { + binding!!.myData.progressBar.visibility = View.VISIBLE + rsapiClient!!.saveProfile(profile)!!.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + binding!!.myData.progressBar.visibility = View.GONE + when (response.code()) { + 200 -> Log.i(TAG, "Successfully saved profile") + 202 -> confirmOk(this@MyDataActivity, R.string.password_email) + 400 -> confirmOk(this@MyDataActivity, R.string.profile_wrong_data) + 401 -> { + logout(view) + confirmOk(this@MyDataActivity, R.string.authorization_failed) + } + + 409 -> confirmOk(this@MyDataActivity, R.string.profile_conflict) + else -> confirmOk( + this@MyDataActivity, + getString(R.string.save_profile_failed, response.code().toString()) + ) } } - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - binding.myData.progressBar.setVisibility(View.GONE); - Log.e(TAG, "Error uploading profile", t); - SimpleDialogs.confirmOk(MyDataActivity.this, - getString(R.string.save_profile_failed, t.getMessage())); + override fun onFailure(call: Call, t: Throwable) { + binding!!.myData.progressBar.visibility = View.GONE + Log.e(TAG, "Error uploading profile", t) + confirmOk( + this@MyDataActivity, + getString(R.string.save_profile_failed, t.message) + ) } - }); + }) } - - saveLocalProfile(profile); - Toast.makeText(this, R.string.preferences_saved, Toast.LENGTH_LONG).show(); + saveLocalProfile(profile) + Toast.makeText(this, R.string.preferences_saved, Toast.LENGTH_LONG).show() } - private Profile createProfileFromUI() { - var newProfile = new Profile( - binding.myData.etNickname.getText().toString().trim(), - license, - binding.myData.cbOwnPhoto.isChecked(), - binding.myData.cbAnonymous.isChecked(), - binding.myData.etLinking.getText().toString().trim(), - binding.myData.etEmail.getText().toString().trim() - ); - - if (this.profile != null) { - newProfile.setEmailVerified(this.profile.getEmailVerified()); + private fun createProfileFromUI(): Profile { + val newProfile = Profile( + binding!!.myData.etNickname.text.toString().trim { it <= ' ' }, + license, + binding!!.myData.cbOwnPhoto.isChecked, + binding!!.myData.cbAnonymous.isChecked, + binding!!.myData.etLinking.text.toString().trim { it <= ' ' }, + binding!!.myData.etEmail.text.toString().trim { it <= ' ' } + ) + if (profile != null) { + newProfile.emailVerified = profile!!.emailVerified } - - return newProfile; + return newProfile } - private void saveLocalProfile(Profile profile) { - baseApplication.setProfile(profile); - setProfileToUI(profile); + private fun saveLocalProfile(profile: Profile?) { + baseApplication.setProfile(profile) + setProfileToUI(profile) } - private boolean isLoginDataAvailable() { - return baseApplication.getAccessToken() != null; - } + private val isLoginDataAvailable: Boolean + private get() = baseApplication.getAccessToken() != null - @Override - public void onBackPressed() { - startActivity(new Intent(this, MainActivity.class)); - finish(); + override fun onBackPressed() { + startActivity(Intent(this, MainActivity::class.java)) + finish() } - public boolean isValid(Profile profile) { - if (StringUtils.isBlank(profile.getNickname())) { - SimpleDialogs.confirmOk(this, R.string.missing_nickname); - return false; + fun isValid(profile: Profile?): Boolean { + if (StringUtils.isBlank(profile!!.nickname)) { + confirmOk(this, R.string.missing_nickname) + return false } - if (!isValidEmail(profile.getEmail())) { - SimpleDialogs.confirmOk(this, R.string.missing_email_address); - return false; + if (!isValidEmail(profile.email)) { + confirmOk(this, R.string.missing_email_address) + return false } - String url = profile.getLink(); + val url = profile.link if (StringUtils.isNotBlank(url) && !isValidHTTPURL(url)) { - SimpleDialogs.confirmOk(this, R.string.missing_link); - return false; + confirmOk(this, R.string.missing_link) + return false } - - return true; + return true } - private boolean isValidHTTPURL(String urlString) { + private fun isValidHTTPURL(urlString: String?): Boolean { try { - var url = new URL(urlString); - if (!"http".equals(url.getProtocol()) && !"https".equals(url.getProtocol())) { - return false; + val url = URL(urlString) + if ("http" != url.protocol && "https" != url.protocol) { + return false } - } catch (MalformedURLException e) { - return false; + } catch (e: MalformedURLException) { + return false } - return true; + return true } - public boolean isValidEmail(CharSequence target) { - return target != null && android.util.Patterns.EMAIL_ADDRESS.matcher(target).matches(); - + fun isValidEmail(target: CharSequence?): Boolean { + return target != null && Patterns.EMAIL_ADDRESS.matcher(target).matches() } - public void onAnonymousChecked(View view) { - if (binding.myData.cbAnonymous.isChecked()) { - binding.myData.etLinking.setVisibility(View.GONE); - binding.myData.tvLinking.setVisibility(View.GONE); + fun onAnonymousChecked(view: View?) { + if (binding!!.myData.cbAnonymous.isChecked) { + binding!!.myData.etLinking.visibility = View.GONE + binding!!.myData.tvLinking.visibility = View.GONE } else { - binding.myData.etLinking.setVisibility(View.VISIBLE); - binding.myData.tvLinking.setVisibility(View.VISIBLE); + binding!!.myData.etLinking.visibility = View.VISIBLE + binding!!.myData.tvLinking.visibility = View.VISIBLE } } - public void login(View view) throws NoSuchAlgorithmException { - Intent intent = new Intent( - Intent.ACTION_VIEW, - rsapiClient.createAuthorizeUri()); - startActivity(intent); - finish(); + @Throws(NoSuchAlgorithmException::class) + fun login(view: View?) { + val intent = Intent( + Intent.ACTION_VIEW, + rsapiClient!!.createAuthorizeUri() + ) + startActivity(intent) + finish() } - public void logout(View view) { - baseApplication.setAccessToken(null); - rsapiClient.clearToken(); - profile = new Profile(); - saveLocalProfile(profile); - binding.myData.profileForm.setVisibility(View.GONE); - binding.myData.loginForm.setVisibility(View.VISIBLE); - Objects.requireNonNull(getSupportActionBar()).setTitle(R.string.login); + fun logout(view: View?) { + baseApplication.setAccessToken(null) + rsapiClient!!.clearToken() + profile = Profile() + saveLocalProfile(profile) + binding!!.myData.profileForm.visibility = View.GONE + binding!!.myData.loginForm.visibility = View.VISIBLE + Objects.requireNonNull(supportActionBar).setTitle(R.string.login) } - public void deleteAccount(View view) { - SimpleDialogs.confirmOkCancel(this, R.string.deleteAccountConfirmation, (d, i) -> rsapiClient.deleteAccount().enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - switch (response.code()) { - case 204: - Log.i(TAG, "Successfully deleted account"); - logout(view); - SimpleDialogs.confirmOk(MyDataActivity.this, R.string.account_deleted); - break; - case 401: - SimpleDialogs.confirmOk(MyDataActivity.this, R.string.authorization_failed); - logout(view); - break; - default: - SimpleDialogs.confirmOk(MyDataActivity.this, - getString(R.string.account_deletion_failed, String.valueOf(response.code()))); - } - } + fun deleteAccount(view: View?) { + SimpleDialogs.confirmOkCancel( + this, + R.string.deleteAccountConfirmation + ) { d: DialogInterface?, i: Int -> + rsapiClient!!.deleteAccount()!! + .enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + when (response.code()) { + 204 -> { + Log.i(TAG, "Successfully deleted account") + logout(view) + confirmOk(this@MyDataActivity, R.string.account_deleted) + } - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Log.e(TAG, "Error deleting account", t); - SimpleDialogs.confirmOk(MyDataActivity.this, - getString(R.string.account_deletion_failed, t.getMessage())); - } - })); - } + 401 -> { + confirmOk(this@MyDataActivity, R.string.authorization_failed) + logout(view) + } - public void changePassword(View view) { - var builder = new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.AlertDialogCustom)); - var passwordBinding = ChangePasswordBinding.inflate(getLayoutInflater()); + else -> confirmOk( + this@MyDataActivity, + getString( + R.string.account_deletion_failed, + response.code().toString() + ) + ) + } + } - builder.setTitle(R.string.bt_change_password) - .setView(passwordBinding.getRoot()) - .setIcon(R.mipmap.ic_launcher) - .setPositiveButton(android.R.string.ok, null) - .setNegativeButton(android.R.string.cancel, (dialog, id) -> dialog.cancel()); - - var alertDialog = builder.create(); - alertDialog.show(); - - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> { - String newPassword = getValidPassword(passwordBinding.password, passwordBinding.passwordRepeat); - if (newPassword == null) { - return; - } - alertDialog.dismiss(); + override fun onFailure(call: Call, t: Throwable) { + Log.e(TAG, "Error deleting account", t) + confirmOk( + this@MyDataActivity, + getString(R.string.account_deletion_failed, t.message) + ) + } + }) + } + } + fun changePassword(view: View?) { + val builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.AlertDialogCustom)) + val passwordBinding = ChangePasswordBinding.inflate( + layoutInflater + ) + builder.setTitle(R.string.bt_change_password) + .setView(passwordBinding.root) + .setIcon(R.mipmap.ic_launcher) + .setPositiveButton(android.R.string.ok, null) + .setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, id: Int -> dialog.cancel() } + val alertDialog = builder.create() + alertDialog.show() + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { v: View? -> + var newPassword: String? = + getValidPassword(passwordBinding.password, passwordBinding.passwordRepeat) + ?: return@setOnClickListener + alertDialog.dismiss() try { - newPassword = URLEncoder.encode(newPassword, String.valueOf(StandardCharsets.UTF_8)); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "Error encoding new password", e); + newPassword = URLEncoder.encode(newPassword, StandardCharsets.UTF_8.toString()) + } catch (e: UnsupportedEncodingException) { + Log.e(TAG, "Error encoding new password", e) } + rsapiClient!!.changePassword(newPassword)!!.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + when (response.code()) { + 200 -> { + Log.i(TAG, "Successfully changed password") + logout(view) + confirmOk(this@MyDataActivity, R.string.password_changed) + } + + 401 -> { + confirmOk(this@MyDataActivity, R.string.authorization_failed) + logout(view) + } - rsapiClient.changePassword(newPassword).enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - switch (response.code()) { - case 200: - Log.i(TAG, "Successfully changed password"); - logout(view); - SimpleDialogs.confirmOk(MyDataActivity.this, R.string.password_changed); - break; - case 401: - SimpleDialogs.confirmOk(MyDataActivity.this, R.string.authorization_failed); - logout(view); - break; - default: - SimpleDialogs.confirmOk(MyDataActivity.this, - getString(R.string.change_password_failed, String.valueOf(response.code()))); + else -> confirmOk( + this@MyDataActivity, + getString(R.string.change_password_failed, response.code().toString()) + ) } } - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Log.e(TAG, "Error changing password", t); - SimpleDialogs.confirmOk(MyDataActivity.this, - getString(R.string.change_password_failed, t.getMessage())); + override fun onFailure(call: Call, t: Throwable) { + Log.e(TAG, "Error changing password", t) + confirmOk( + this@MyDataActivity, + getString(R.string.change_password_failed, t.message) + ) } - }); - }); - + }) + } } - private String getValidPassword(EditText etNewPassword, EditText etPasswordRepeat) { - var newPassword = etNewPassword.getText().toString().trim(); - - if (newPassword.length() < 8) { - Toast.makeText(MyDataActivity.this, R.string.password_too_short, Toast.LENGTH_LONG).show(); - return null; + private fun getValidPassword(etNewPassword: EditText, etPasswordRepeat: EditText): String? { + val newPassword = etNewPassword.text.toString().trim { it <= ' ' } + if (newPassword.length < 8) { + Toast.makeText(this@MyDataActivity, R.string.password_too_short, Toast.LENGTH_LONG) + .show() + return null } - if (!newPassword.equals(etPasswordRepeat.getText().toString().trim())) { - Toast.makeText(MyDataActivity.this, R.string.password_repeat_fail, Toast.LENGTH_LONG).show(); - return null; + if (newPassword != etPasswordRepeat.text.toString().trim { it <= ' ' }) { + Toast.makeText(this@MyDataActivity, R.string.password_repeat_fail, Toast.LENGTH_LONG) + .show() + return null } - return newPassword; + return newPassword } - public void requestEmailVerification(View view) { - SimpleDialogs.confirmOkCancel(this, R.string.requestEmailVerification, (dialogInterface, i) -> rsapiClient.resendEmailVerification().enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - if (response.code() == 200) { - Log.i(TAG, "Successfully requested email verification"); - Toast.makeText(MyDataActivity.this, R.string.emailVerificationRequested, Toast.LENGTH_LONG).show(); - } else { - Toast.makeText(MyDataActivity.this, R.string.emailVerificationRequestFailed, Toast.LENGTH_LONG).show(); - } - } + fun requestEmailVerification(view: View?) { + SimpleDialogs.confirmOkCancel( + this, + R.string.requestEmailVerification + ) { dialogInterface: DialogInterface?, i: Int -> + rsapiClient!!.resendEmailVerification()!! + .enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.code() == 200) { + Log.i(TAG, "Successfully requested email verification") + Toast.makeText( + this@MyDataActivity, + R.string.emailVerificationRequested, + Toast.LENGTH_LONG + ).show() + } else { + Toast.makeText( + this@MyDataActivity, + R.string.emailVerificationRequestFailed, + Toast.LENGTH_LONG + ).show() + } + } - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Log.e(TAG, "Error requesting email verification", t); - Toast.makeText(MyDataActivity.this, R.string.emailVerificationRequestFailed, Toast.LENGTH_LONG).show(); - } - })); + override fun onFailure(call: Call, t: Throwable) { + Log.e(TAG, "Error requesting email verification", t) + Toast.makeText( + this@MyDataActivity, + R.string.emailVerificationRequestFailed, + Toast.LENGTH_LONG + ).show() + } + }) + } } -} + companion object { + private val TAG = MyDataActivity::class.java.simpleName + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/NearbyNotificationService.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/NearbyNotificationService.kt index f6555c92..409a4b1a 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/NearbyNotificationService.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/NearbyNotificationService.kt @@ -1,193 +1,152 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; - -import static android.app.PendingIntent.FLAG_IMMUTABLE; - -import android.Manifest; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.os.Binder; -import android.os.Bundle; -import android.os.IBinder; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; -import androidx.core.content.ContextCompat; - -import java.util.ArrayList; -import java.util.List; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.db.DbAdapter; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station; -import de.bahnhoefe.deutschlands.bahnhofsfotos.notification.NearbyBahnhofNotificationManager; -import de.bahnhoefe.deutschlands.bahnhofsfotos.notification.NearbyBahnhofNotificationManagerFactory; - -public class NearbyNotificationService extends Service implements LocationListener { - - // The minimum distance to change Updates in meters - private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 1000; // 1km - - // The minimum time between updates in milliseconds - private static final long MIN_TIME_BW_UPDATES = 10000; // 10 seconds - - private static final double MIN_NOTIFICATION_DISTANCE = 1.0d; // km - private static final double EARTH_CIRCUMFERENCE = 40075.017d; // km at equator - private static final int ONGOING_NOTIFICATION_ID = 0xdeadbeef; - - private final String TAG = NearbyNotificationService.class.getSimpleName(); - private List nearStations; - private Location myPos = new Location((String)null); - - private LocationManager locationManager; - - private NearbyBahnhofNotificationManager notifiedStationManager; - private BaseApplication baseApplication = null; - private DbAdapter dbAdapter = null; - - /** - * The intent action to use to bind to this service's status interface. - */ - public static final String STATUS_INTERFACE = NearbyNotificationService.class.getPackage().getName() + ".Status"; - - public NearbyNotificationService() { - } - - @Override - public void onCreate() { - Log.i(TAG, "About to create"); - super.onCreate(); - myPos.setLatitude(50d); - myPos.setLongitude(8d); - nearStations = new ArrayList<>(0); // no markers until we know where we are - notifiedStationManager = null; - baseApplication = (BaseApplication)getApplication(); - dbAdapter = baseApplication.getDbAdapter(); +package de.bahnhoefe.deutschlands.bahnhofsfotos + +import android.Manifest +import android.app.Notification +import android.app.PendingIntent +import android.app.Service +import android.content.Intent +import android.content.pm.PackageManager +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import android.os.Binder +import android.os.Bundle +import android.os.IBinder +import android.util.Log +import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat +import de.bahnhoefe.deutschlands.bahnhofsfotos.db.DbAdapter +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station +import de.bahnhoefe.deutschlands.bahnhofsfotos.notification.NearbyBahnhofNotificationManager +import de.bahnhoefe.deutschlands.bahnhofsfotos.notification.NearbyBahnhofNotificationManagerFactory +import kotlin.math.abs +import kotlin.math.cos +import kotlin.math.pow +import kotlin.math.sqrt + +class NearbyNotificationService : Service(), LocationListener { + private val TAG = NearbyNotificationService::class.java.simpleName + private var nearStations = listOf() + private var myPos: Location? = Location(null as String?) + private var locationManager: LocationManager? = null + private var notifiedStationManager: NearbyBahnhofNotificationManager? = null + private lateinit var baseApplication: BaseApplication + private lateinit var dbAdapter: DbAdapter + override fun onCreate() { + super.onCreate() + myPos!!.latitude = 50.0 + myPos!!.longitude = 8.0 + notifiedStationManager = null + baseApplication = application as BaseApplication + dbAdapter = baseApplication.dbAdapter } - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - cancelNotification(); - - Log.i(TAG, "Received start command"); - - var resultIntent = new Intent(this, MainActivity.class); - var resultPendingIntent = - PendingIntent.getActivity( - this, - 0, - resultIntent, - PendingIntent.FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE - ); - - NearbyBahnhofNotificationManager.createChannel(this); + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + cancelNotification() + Log.i(TAG, "Received start command") + val resultIntent = Intent(this, MainActivity::class.java) + val resultPendingIntent = PendingIntent.getActivity( + this, + 0, + resultIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + NearbyBahnhofNotificationManager.Companion.createChannel(this) // show a permanent notification to indicate that position detection is running - var ongoingNotification = new NotificationCompat.Builder(this, NearbyBahnhofNotificationManager.CHANNEL_ID) + val ongoingNotification: Notification = + NotificationCompat.Builder(this, NearbyBahnhofNotificationManager.Companion.CHANNEL_ID) .setSmallIcon(R.drawable.ic_launcher) .setContentTitle(getString(R.string.nearby_notification_active)) .setOngoing(true) .setLocalOnly(true) .setContentIntent(resultPendingIntent) - .build(); - var notificationManager = NotificationManagerCompat.from(this); - notificationManager.notify(ONGOING_NOTIFICATION_ID, ongoingNotification); - - registerLocationManager(); - - return START_STICKY; + .build() + val notificationManager = NotificationManagerCompat.from(this) + if (ActivityCompat.checkSelfPermission( + this, + Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + return START_STICKY + } + notificationManager.notify(ONGOING_NOTIFICATION_ID, ongoingNotification) + registerLocationManager() + return START_STICKY } - @Override - public void onLowMemory() { + override fun onLowMemory() { // stop tracking - super.onLowMemory(); - stopSelf(); + super.onLowMemory() + stopSelf() } - @Override - public void onDestroy() { - Log.i(TAG, "Service gets destroyed"); + override fun onDestroy() { + Log.i(TAG, "Service gets destroyed") try { - cancelNotification(); - unregisterLocationManager(); - } catch (Throwable t) { - Log.wtf(TAG, "Unknown problem when trying to de-register from GPS updates", t); + cancelNotification() + unregisterLocationManager() + } catch (t: Throwable) { + Log.wtf(TAG, "Unknown problem when trying to de-register from GPS updates", t) } // Cancel the ongoing notification - var notificationManager = - NotificationManagerCompat.from(this); - notificationManager.cancel(ONGOING_NOTIFICATION_ID); - - super.onDestroy(); + val notificationManager = NotificationManagerCompat.from(this) + notificationManager.cancel(ONGOING_NOTIFICATION_ID) + super.onDestroy() } - private void cancelNotification() { + private fun cancelNotification() { if (notifiedStationManager != null) { - notifiedStationManager.destroy(); - notifiedStationManager = null; + notifiedStationManager!!.destroy() + notifiedStationManager = null } } - @Override - public void onLocationChanged(@NonNull Location location) { - Log.i(TAG, "Received new location: " + location); + override fun onLocationChanged(location: Location) { + Log.i(TAG, "Received new location: $location") try { - myPos = location; + myPos = location // check if currently advertised station is still in range if (notifiedStationManager != null) { - if (calcDistance(notifiedStationManager.getStation()) > MIN_NOTIFICATION_DISTANCE) { - cancelNotification(); + if (calcDistance(notifiedStationManager!!.station) > MIN_NOTIFICATION_DISTANCE) { + cancelNotification() } } - Log.d(TAG, "Reading matching stations from local database"); - readStations(); + Log.d(TAG, "Reading matching stations from local database") + readStations() // check if a user notification is appropriate - checkNearestStation(); - } catch (Throwable t) { - Log.e(TAG, "Unknown Problem arised during location change handling", t); + checkNearestStation() + } catch (t: Throwable) { + Log.e(TAG, "Unknown Problem arised during location change handling", t) } } - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - - } - - @Override - public void onProviderEnabled(@NonNull String provider) { - - } - - @Override - public void onProviderDisabled(@NonNull String provider) { - + @Deprecated("Deprecated in Java") + override fun onStatusChanged(provider: String, status: Int, extras: Bundle) { } - private void checkNearestStation() { - double minDist = 3e3; - Station nearest = null; - for (var station : nearStations) { - var dist = calcDistance(station); + override fun onProviderEnabled(provider: String) {} + override fun onProviderDisabled(provider: String) {} + private fun checkNearestStation() { + var minDist = 3e3 + var nearest: Station? = null + for (station in nearStations) { + val dist = calcDistance(station) if (dist < minDist) { - nearest = station; - minDist = dist; + nearest = station + minDist = dist } } if (nearest != null && minDist < MIN_NOTIFICATION_DISTANCE) { - notifyNearest(nearest, minDist); - Log.i(TAG, "Issued notification to user"); + notifyNearest(nearest, minDist) + Log.i(TAG, "Issued notification to user") } else { - Log.d(TAG, "No notification - nearest station was " + minDist + " km away: " + nearest); + Log.d(TAG, "No notification - nearest station was $minDist km away: $nearest") } } @@ -199,17 +158,22 @@ public class NearbyNotificationService extends Service implements LocationListen * @param nearest the station nearest to the current position * @param distance the distance of the station to the current position */ - private void notifyNearest(Station nearest, double distance) { + private fun notifyNearest(nearest: Station, distance: Double) { if (notifiedStationManager != null) { - if (notifiedStationManager.getStation().equals(nearest)) { - return; // Notification für diesen Bahnhof schon angezeigt + notifiedStationManager = if (nearest == notifiedStationManager!!.station) { + return // Notification für diesen Bahnhof schon angezeigt } else { - notifiedStationManager.destroy(); - notifiedStationManager = null; + notifiedStationManager!!.destroy() + null } } - notifiedStationManager = NearbyBahnhofNotificationManagerFactory.create(this, nearest, distance, dbAdapter.fetchCountriesWithProviderApps(baseApplication.getCountryCodes())); - notifiedStationManager.notifyUser(); + notifiedStationManager = NearbyBahnhofNotificationManagerFactory.create( + this, + nearest, + distance, + dbAdapter.fetchCountriesWithProviderApps(baseApplication.countryCodes) + ) + notifiedStationManager!!.notifyUser() } /** @@ -218,78 +182,96 @@ public class NearbyNotificationService extends Service implements LocationListen * @param station the station to calculate the distance to * @return the distance */ - private double calcDistance(Station station) { + private fun calcDistance(station: Station): Double { // Wir nähern für glatte Oberflächen, denn wir sind an Abständen kleiner 1km interessiert - var lateralDiff = myPos.getLatitude() - station.getLat(); - var longDiff = (Math.abs(myPos.getLatitude()) < 89.99d) ? - (myPos.getLongitude() - station.getLon()) * Math.cos(myPos.getLatitude() / 180 * Math.PI) : - 0.0d; // at the poles, longitude doesn't matter + val lateralDiff = myPos!!.latitude - station.lat + val longDiff = + if (abs(myPos!!.latitude) < 89.99) (myPos!!.longitude - station.lon) * cos( + myPos!!.latitude / 180 * Math.PI + ) else 0.0 // at the poles, longitude doesn't matter // simple Pythagoras now. - return Math.sqrt(Math.pow(lateralDiff, 2.0d) + Math.pow(longDiff, 2.0d)) * EARTH_CIRCUMFERENCE / 360.0d; + return sqrt( + lateralDiff.pow(2.0) + longDiff.pow(2.0) + ) * EARTH_CIRCUMFERENCE / 360.0 } - public void registerLocationManager() { - + private fun registerLocationManager() { try { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - Log.w(TAG, "No Location Permission"); - return; + if (ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_COARSE_LOCATION + ) != PackageManager.PERMISSION_GRANTED + ) { + Log.w(TAG, "No Location Permission") + return } - - locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE); + locationManager = + applicationContext.getSystemService(LOCATION_SERVICE) as LocationManager // getting GPS status - var isGPSEnabled = locationManager - .isProviderEnabled(LocationManager.GPS_PROVIDER); + val isGPSEnabled = locationManager!! + .isProviderEnabled(LocationManager.GPS_PROVIDER) // if GPS Enabled get lat/long using GPS Services if (isGPSEnabled) { - locationManager.requestLocationUpdates( - LocationManager.GPS_PROVIDER, - MIN_TIME_BW_UPDATES, - MIN_DISTANCE_CHANGE_FOR_UPDATES, this); - Log.d(TAG, "GPS Enabled"); + locationManager!!.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + MIN_TIME_BW_UPDATES, + MIN_DISTANCE_CHANGE_FOR_UPDATES.toFloat(), this + ) + Log.d(TAG, "GPS Enabled") if (locationManager != null) { - myPos = locationManager - .getLastKnownLocation(LocationManager.GPS_PROVIDER); + myPos = locationManager!! + .getLastKnownLocation(LocationManager.GPS_PROVIDER) } } else { // getting network status - var isNetworkEnabled = locationManager - .isProviderEnabled(LocationManager.NETWORK_PROVIDER); + val isNetworkEnabled = locationManager!! + .isProviderEnabled(LocationManager.NETWORK_PROVIDER) // First get location from Network Provider if (isNetworkEnabled) { - locationManager.requestLocationUpdates( - LocationManager.NETWORK_PROVIDER, - MIN_TIME_BW_UPDATES, - MIN_DISTANCE_CHANGE_FOR_UPDATES, this); - Log.d(TAG, "Network Location enabled"); + locationManager!!.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, + MIN_TIME_BW_UPDATES, + MIN_DISTANCE_CHANGE_FOR_UPDATES.toFloat(), this + ) + Log.d(TAG, "Network Location enabled") if (locationManager != null) { - myPos = locationManager - .getLastKnownLocation(LocationManager.NETWORK_PROVIDER); + myPos = locationManager!! + .getLastKnownLocation(LocationManager.NETWORK_PROVIDER) } } } - } catch (Exception e) { - Log.e(TAG, "Error registering LocationManager", e); - var b = new Bundle(); - b.putString("error", "Error registering LocationManager: " + e); - locationManager = null; - myPos = null; - return; + } catch (e: Exception) { + Log.e(TAG, "Error registering LocationManager", e) + val b = Bundle() + b.putString("error", "Error registering LocationManager: $e") + locationManager = null + myPos = null + return } - Log.i(TAG, "LocationManager registered"); + Log.i(TAG, "LocationManager registered") } - private void unregisterLocationManager() { + private fun unregisterLocationManager() { if (locationManager != null) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { - locationManager.removeUpdates(this); + if (ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + locationManager!!.removeUpdates(this) } - locationManager = null; + locationManager = null } - Log.i(TAG, "LocationManager unregistered"); + Log.i(TAG, "LocationManager unregistered") } /** @@ -297,34 +279,52 @@ public class NearbyNotificationService extends Service implements LocationListen * Currently, can only be used to query the service state, i.e. if the location tracking * is switched off or on with photo or on without photo. */ - public static class StatusBinder extends Binder { - } + class StatusBinder : Binder() /** * Bind to interfaces provided by this service. Currently implemented: - *
    - *
  • STATUS_INTERFACE: Returns a StatusBinder that can be used to query the tracking status
  • - *
+ * + * * STATUS_INTERFACE: Returns a StatusBinder that can be used to query the tracking status + * * * @param intent an Intent giving the intended action * @return a Binder instance suitable for the intent supplied, or null if none matches. */ - @Override - public IBinder onBind(Intent intent) { - if (STATUS_INTERFACE.equals(intent.getAction())) { - return new StatusBinder(); + override fun onBind(intent: Intent): IBinder? { + return if (STATUS_INTERFACE == intent.action) { + StatusBinder() } else { - return null; + null } } - private void readStations() { + private fun readStations() { try { - Log.i(TAG, "Lade nahegelegene Bahnhoefe"); - nearStations = dbAdapter.getStationByLatLngRectangle(myPos.getLatitude(), myPos.getLongitude(), baseApplication.getStationFilter()); - } catch (Exception e) { - Log.e(TAG, "Datenbank konnte nicht geöffnet werden", e); + Log.i(TAG, "Lade nahegelegene Bahnhoefe") + nearStations = dbAdapter.getStationByLatLngRectangle( + myPos!!.latitude, + myPos!!.longitude, + baseApplication.stationFilter + ) + } catch (e: Exception) { + Log.e(TAG, "Datenbank konnte nicht geöffnet werden", e) } } -} + companion object { + // The minimum distance to change Updates in meters + private const val MIN_DISTANCE_CHANGE_FOR_UPDATES: Long = 1000 // 1km + + // The minimum time between updates in milliseconds + private const val MIN_TIME_BW_UPDATES: Long = 10000 // 10 seconds + private const val MIN_NOTIFICATION_DISTANCE = 1.0 // km + private const val EARTH_CIRCUMFERENCE = 40075.017 // km at equator + private const val ONGOING_NOTIFICATION_ID = -0x21524111 + + /** + * The intent action to use to bind to this service's status interface. + */ + val STATUS_INTERFACE = + (NearbyNotificationService::class.java.getPackage()?.name ?: "") + ".Status" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/OutboxActivity.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/OutboxActivity.kt index f68efc0b..1fa5b22e 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/OutboxActivity.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/OutboxActivity.kt @@ -1,151 +1,156 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; - -import static java.util.stream.Collectors.toList; - -import android.app.AlertDialog; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.view.ContextThemeWrapper; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; - -import java.util.List; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityOutboxBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.db.DbAdapter; -import de.bahnhoefe.deutschlands.bahnhofsfotos.db.OutboxAdapter; -import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.SimpleDialogs; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxStateQuery; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Upload; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.FileUtils; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class OutboxActivity extends AppCompatActivity { - - private static final String TAG = OutboxActivity.class.getSimpleName(); - - private OutboxAdapter adapter; - private DbAdapter dbAdapter; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - var baseApplication = (BaseApplication) getApplication(); - dbAdapter = baseApplication.getDbAdapter(); - - var binding = ActivityOutboxBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - adapter = new OutboxAdapter(OutboxActivity.this, dbAdapter.getOutbox()); - binding.lstUploads.setAdapter(adapter); +package de.bahnhoefe.deutschlands.bahnhofsfotos + +import android.app.AlertDialog +import android.content.DialogInterface +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.ContextThemeWrapper +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.AdapterView +import android.widget.AdapterView.OnItemClickListener +import android.widget.AdapterView.OnItemLongClickListener +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityOutboxBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.db.DbAdapter +import de.bahnhoefe.deutschlands.bahnhofsfotos.db.OutboxAdapter +import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.SimpleDialogs +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxStateQuery +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Upload +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.FileUtils +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.util.stream.Collectors + +class OutboxActivity : AppCompatActivity() { + private var adapter: OutboxAdapter? = null + private var dbAdapter: DbAdapter? = null + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val baseApplication = application as BaseApplication + dbAdapter = baseApplication.dbAdapter + val binding = ActivityOutboxBinding.inflate( + layoutInflater + ) + setContentView(binding.root) + adapter = OutboxAdapter(this@OutboxActivity, dbAdapter.getOutbox()) + binding.lstUploads.adapter = adapter // item click - binding.lstUploads.setOnItemClickListener((parent, view, position, id) -> { - var upload = dbAdapter.getUploadById(id); - Intent intent; - if (upload.isProblemReport()) { - intent = new Intent(OutboxActivity.this, ProblemReportActivity.class); - intent.putExtra(ProblemReportActivity.EXTRA_UPLOAD, upload); - } else { - intent = new Intent(OutboxActivity.this, UploadActivity.class); - intent.putExtra(UploadActivity.EXTRA_UPLOAD, upload); - } - startActivity(intent); - }); - - binding.lstUploads.setOnItemLongClickListener((parent, view, position, id) -> { - var uploadId = String.valueOf(id); - SimpleDialogs.confirmOkCancel(OutboxActivity.this, getResources().getString(R.string.delete_upload, uploadId), (dialog, which) -> { - dbAdapter.deleteUpload(id); - FileUtils.deleteQuietly(FileUtils.getStoredMediaFile(this, id)); - adapter.changeCursor(dbAdapter.getOutbox()); - }); - return true; - }); - - var query = dbAdapter.getPendingUploads(true).stream() - .map(upload -> new InboxStateQuery(upload.getRemoteId())) - .collect(toList()); - - baseApplication.getRsapiClient().queryUploadState(query).enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call> call, @NonNull Response> response) { - if (response.isSuccessful()) { - var stateQueries = response.body(); - if (stateQueries != null) { - dbAdapter.updateUploadStates(stateQueries); - adapter.changeCursor(dbAdapter.getOutbox()); - } - } else if (response.code() == 401) { - baseApplication.setAccessToken(null); - baseApplication.getRsapiClient().clearToken(); - Toast.makeText(OutboxActivity.this, R.string.authorization_failed, Toast.LENGTH_LONG).show(); - startActivity(new Intent(OutboxActivity.this, MyDataActivity.class)); - finish(); + binding.lstUploads.onItemClickListener = + OnItemClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long -> + val upload = dbAdapter!!.getUploadById(id) + val intent: Intent + if (upload!!.isProblemReport) { + intent = Intent(this@OutboxActivity, ProblemReportActivity::class.java) + intent.putExtra(ProblemReportActivity.Companion.EXTRA_UPLOAD, upload) } else { - Log.w(TAG, "Upload states not processable"); + intent = Intent(this@OutboxActivity, UploadActivity::class.java) + intent.putExtra(UploadActivity.Companion.EXTRA_UPLOAD, upload) + } + startActivity(intent) + } + binding.lstUploads.onItemLongClickListener = + OnItemLongClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long -> + val uploadId = id.toString() + SimpleDialogs.confirmOkCancel( + this@OutboxActivity, + resources.getString(R.string.delete_upload, uploadId) + ) { dialog: DialogInterface?, which: Int -> + dbAdapter!!.deleteUpload(id) + FileUtils.deleteQuietly(FileUtils.getStoredMediaFile(this, id)) + adapter!!.changeCursor(dbAdapter.getOutbox()) } + true } + val query = dbAdapter!!.getPendingUploads(true).stream() + .map { upload: Upload? -> + InboxStateQuery( + upload!!.remoteId + ) + } + .collect(Collectors.toList()) + baseApplication.rsapiClient.queryUploadState(query) + .enqueue(object : Callback?> { + override fun onResponse( + call: Call?>, + response: Response?> + ) { + if (response.isSuccessful) { + val stateQueries = response.body() + if (stateQueries != null) { + dbAdapter!!.updateUploadStates(stateQueries) + adapter!!.changeCursor(dbAdapter.getOutbox()) + } + } else if (response.code() == 401) { + baseApplication.accessToken = null + baseApplication.rsapiClient.clearToken() + Toast.makeText( + this@OutboxActivity, + R.string.authorization_failed, + Toast.LENGTH_LONG + ).show() + startActivity(Intent(this@OutboxActivity, MyDataActivity::class.java)) + finish() + } else { + Log.w(TAG, "Upload states not processable") + } + } - @Override - public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - Log.e(TAG, "Error retrieving upload state", t); - Toast.makeText(OutboxActivity.this, + override fun onFailure(call: Call?>, t: Throwable) { + Log.e(TAG, "Error retrieving upload state", t) + Toast.makeText( + this@OutboxActivity, R.string.error_retrieving_upload_state, - Toast.LENGTH_LONG).show(); - } - }); + Toast.LENGTH_LONG + ).show() + } + }) } - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - menu.clear(); - getMenuInflater().inflate(R.menu.outbox, menu); - return super.onPrepareOptionsMenu(menu); + override fun onPrepareOptionsMenu(menu: Menu): Boolean { + menu.clear() + menuInflater.inflate(R.menu.outbox, menu) + return super.onPrepareOptionsMenu(menu) } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.nav_delete_processed_uploads) { - deleteCompletedUploads(); - return true; + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.nav_delete_processed_uploads) { + deleteCompletedUploads() + return true } - return super.onOptionsItemSelected(item); + return super.onOptionsItemSelected(item) } - private void deleteCompletedUploads() { - var uploads = dbAdapter.getCompletedUploads(); - if (uploads.isEmpty()) { - return; + private fun deleteCompletedUploads() { + val uploads = dbAdapter.getCompletedUploads() + if (uploads!!.isEmpty()) { + return } - - new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.AlertDialogCustom)) - .setIcon(R.mipmap.ic_launcher) - .setTitle(R.string.confirm_delete_processed_uploads) - .setPositiveButton(R.string.button_ok_text, (dialog, which) -> { - for (Upload upload : uploads) { - dbAdapter.deleteUpload(upload.getId()); - FileUtils.deleteQuietly(FileUtils.getStoredMediaFile(this, upload.getId())); - } - adapter.changeCursor(dbAdapter.getOutbox()); - }) - .setNegativeButton(R.string.button_cancel_text, null) - .create().show(); + AlertDialog.Builder(ContextThemeWrapper(this, R.style.AlertDialogCustom)) + .setIcon(R.mipmap.ic_launcher) + .setTitle(R.string.confirm_delete_processed_uploads) + .setPositiveButton(R.string.button_ok_text) { dialog: DialogInterface?, which: Int -> + for (upload in uploads) { + dbAdapter!!.deleteUpload(upload!!.id!!) + FileUtils.deleteQuietly(FileUtils.getStoredMediaFile(this, upload!!.id)) + } + adapter!!.changeCursor(dbAdapter.getOutbox()) + } + .setNegativeButton(R.string.button_cancel_text, null) + .create().show() } - @Override - protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - super.onActivityResult(requestCode, resultCode, data); - adapter.changeCursor(dbAdapter.getOutbox()); + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + adapter!!.changeCursor(dbAdapter.getOutbox()) } + companion object { + private val TAG = OutboxActivity::class.java.simpleName + } } \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/PhotoPagerAdapter.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/PhotoPagerAdapter.kt index 2e3cb7fa..d76c3dd5 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/PhotoPagerAdapter.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/PhotoPagerAdapter.kt @@ -1,70 +1,49 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.github.chrisbanes.photoview.PhotoView; - -import java.util.ArrayList; -import java.util.List; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PageablePhoto; - -public class PhotoPagerAdapter extends RecyclerView.Adapter { - - private final List pageablePhotos = new ArrayList<>(); - - private final Context context; - - public PhotoPagerAdapter(Context context) { - this.context = context; - } - - public int addPageablePhoto(PageablePhoto pageablePhoto) { - pageablePhotos.add(pageablePhoto); - notifyDataSetChanged(); - return pageablePhotos.size() - 1; +package de.bahnhoefe.deutschlands.bahnhofsfotos + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.github.chrisbanes.photoview.PhotoView +import de.bahnhoefe.deutschlands.bahnhofsfotos.PhotoPagerAdapter.PhotoViewHolder +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PageablePhoto + +class PhotoPagerAdapter(private val context: Context) : RecyclerView.Adapter() { + private val pageablePhotos: MutableList = ArrayList() + fun addPageablePhoto(pageablePhoto: PageablePhoto): Int { + pageablePhotos.add(pageablePhoto) + notifyDataSetChanged() + return pageablePhotos.size - 1 } - @NonNull - @Override - public PhotoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - var view = LayoutInflater.from(context).inflate(R.layout.photo_view_item, parent, false); - return new PhotoViewHolder(view); + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoViewHolder { + val view = LayoutInflater.from(context).inflate(R.layout.photo_view_item, parent, false) + return PhotoViewHolder(view) } - @Override - public void onBindViewHolder(@NonNull PhotoViewHolder holder, int position) { - var pageablePhoto = getPageablePhotoAtPosition(position); + override fun onBindViewHolder(holder: PhotoViewHolder, position: Int) { + val pageablePhoto = getPageablePhotoAtPosition(position) if (pageablePhoto == null) { - holder.photoView.setImageResource(R.drawable.photo_missing); + holder.photoView.setImageResource(R.drawable.photo_missing) } else { - holder.photoView.setImageBitmap(pageablePhoto.getBitmap()); + holder.photoView.setImageBitmap(pageablePhoto.bitmap) } } - public PageablePhoto getPageablePhotoAtPosition(int position) { - return pageablePhotos.isEmpty() ? null : pageablePhotos.get(position); + fun getPageablePhotoAtPosition(position: Int): PageablePhoto? { + return if (pageablePhotos.isEmpty()) null else pageablePhotos[position] } - @Override - public int getItemCount() { - return pageablePhotos.isEmpty() ? 1 : pageablePhotos.size(); + override fun getItemCount(): Int { + return if (pageablePhotos.isEmpty()) 1 else pageablePhotos.size } - public static class PhotoViewHolder extends RecyclerView.ViewHolder { - - PhotoView photoView; + class PhotoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + var photoView: PhotoView - public PhotoViewHolder(@NonNull View itemView) { - super(itemView); - photoView = itemView.findViewById(R.id.photoView); + init { + photoView = itemView.findViewById(R.id.photoView) } } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/ProblemReportActivity.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/ProblemReportActivity.kt index 369193f3..05dd2f8e 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/ProblemReportActivity.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/ProblemReportActivity.kt @@ -1,321 +1,334 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; - -import android.app.TaskStackBuilder; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.app.NavUtils; - -import com.google.gson.Gson; - -import org.apache.commons.lang3.StringUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ReportProblemBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.SimpleDialogs; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxResponse; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxStateQuery; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProblemReport; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProblemType; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Upload; -import de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi.RSAPIClient; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class ProblemReportActivity extends AppCompatActivity { - - private static final String TAG = ProblemReportActivity.class.getSimpleName(); - - public static final String EXTRA_UPLOAD = "EXTRA_UPLOAD"; - public static final String EXTRA_STATION = "EXTRA_STATION"; - public static final String EXTRA_PHOTO_ID = "EXTRA_PHOTO_ID"; - - private BaseApplication baseApplication; - private RSAPIClient rsapiClient; - private ReportProblemBinding binding; - - private Upload upload; - private Station station; - private Long photoId; - private final ArrayList problemTypes = new ArrayList<>(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ReportProblemBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - baseApplication = (BaseApplication) getApplication(); - rsapiClient = baseApplication.getRsapiClient(); - - Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); - - problemTypes.add(getString(R.string.problem_please_specify)); - for (var type : ProblemType.values()) { - problemTypes.add(getString(type.getMessageId())); +package de.bahnhoefe.deutschlands.bahnhofsfotos + +import android.app.TaskStackBuilder +import android.content.DialogInterface +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.EditText +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.NavUtils +import com.google.gson.Gson +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ReportProblemBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.SimpleDialogs +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxResponse +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxStateQuery +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProblemReport +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProblemType +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Upload +import de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi.RSAPIClient +import org.apache.commons.lang3.StringUtils +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.util.Objects + +class ProblemReportActivity : AppCompatActivity() { + private var baseApplication: BaseApplication? = null + private var rsapiClient: RSAPIClient? = null + private var binding: ReportProblemBinding? = null + private var upload: Upload? = null + private var station: Station? = null + private var photoId: Long? = null + private val problemTypes = ArrayList() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ReportProblemBinding.inflate( + layoutInflater + ) + setContentView(binding!!.root) + baseApplication = application as BaseApplication + rsapiClient = baseApplication.getRsapiClient() + Objects.requireNonNull(supportActionBar).setDisplayHomeAsUpEnabled(true) + problemTypes.add(getString(R.string.problem_please_specify)) + for (type in ProblemType.values()) { + problemTypes.add(getString(type.messageId)) } - var adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, problemTypes); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - binding.problemType.setAdapter(adapter); - - binding.problemType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(final AdapterView parent, final View view, final int position, final long id) { + val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, problemTypes) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding!!.problemType.adapter = adapter + binding!!.problemType.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View, + position: Int, + id: Long + ) { if (position > 0) { - var type = ProblemType.values()[position - 1]; - setCoordsVisible(type == ProblemType.WRONG_LOCATION); - setTitleVisible(type == ProblemType.WRONG_NAME); + val type = ProblemType.values()[position - 1] + setCoordsVisible(type === ProblemType.WRONG_LOCATION) + setTitleVisible(type === ProblemType.WRONG_NAME) } else { - setCoordsVisible(false); - setTitleVisible(false); + setCoordsVisible(false) + setTitleVisible(false) } } - @Override - public void onNothingSelected(final AdapterView parent) { - setCoordsVisible(false); - setTitleVisible(false); + override fun onNothingSelected(parent: AdapterView<*>?) { + setCoordsVisible(false) + setTitleVisible(false) } - }); - - if (!baseApplication.isLoggedIn()) { - Toast.makeText(this, R.string.please_login, Toast.LENGTH_LONG).show(); - startActivity(new Intent(ProblemReportActivity.this, MyDataActivity.class)); - finish(); - return; } - - if (!baseApplication.getProfile().getEmailVerified()) { - SimpleDialogs.confirmOk(this, R.string.email_unverified_for_problem_report, (dialog, view) -> { - startActivity(new Intent(ProblemReportActivity.this, MyDataActivity.class)); - finish(); - }); - return; + if (!baseApplication!!.isLoggedIn) { + Toast.makeText(this, R.string.please_login, Toast.LENGTH_LONG).show() + startActivity(Intent(this@ProblemReportActivity, MyDataActivity::class.java)) + finish() + return } - - onNewIntent(getIntent()); + if (!baseApplication.getProfile().emailVerified) { + SimpleDialogs.confirmOk( + this, + R.string.email_unverified_for_problem_report + ) { dialog: DialogInterface?, view: Int -> + startActivity(Intent(this@ProblemReportActivity, MyDataActivity::class.java)) + finish() + } + return + } + onNewIntent(intent) } - private void setCoordsVisible(boolean visible) { - binding.tvNewCoords.setVisibility(visible ? View.VISIBLE : View.GONE); - binding.etNewLatitude.setVisibility(visible ? View.VISIBLE : View.GONE); - binding.etNewLongitude.setVisibility(visible ? View.VISIBLE : View.GONE); + private fun setCoordsVisible(visible: Boolean) { + binding!!.tvNewCoords.visibility = if (visible) View.VISIBLE else View.GONE + binding!!.etNewLatitude.visibility = if (visible) View.VISIBLE else View.GONE + binding!!.etNewLongitude.visibility = if (visible) View.VISIBLE else View.GONE } - private void setTitleVisible(boolean visible) { - binding.tvNewTitle.setVisibility(visible ? View.VISIBLE : View.GONE); - binding.etNewTitle.setVisibility(visible ? View.VISIBLE : View.GONE); + private fun setTitleVisible(visible: Boolean) { + binding!!.tvNewTitle.visibility = if (visible) View.VISIBLE else View.GONE + binding!!.etNewTitle.visibility = if (visible) View.VISIBLE else View.GONE } - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) if (intent != null) { - upload = (Upload) intent.getSerializableExtra(EXTRA_UPLOAD); - station = (Station) intent.getSerializableExtra(EXTRA_STATION); - photoId = (Long) intent.getSerializableExtra(EXTRA_PHOTO_ID); - - if (upload != null && upload.isProblemReport()) { - binding.etProblemComment.setText(upload.getComment()); - binding.etNewLatitude.setText(upload.getLat() != null ? upload.getLat().toString() : ""); - binding.etNewLongitude.setText(upload.getLon() != null ? upload.getLon().toString() : ""); - - int selected = upload.getProblemType().ordinal() + 1; - binding.problemType.setSelection(selected); - + upload = intent.getSerializableExtra(EXTRA_UPLOAD) as Upload? + station = intent.getSerializableExtra(EXTRA_STATION) as Station? + photoId = intent.getSerializableExtra(EXTRA_PHOTO_ID) as Long? + if (upload != null && upload!!.isProblemReport) { + binding!!.etProblemComment.setText(upload!!.comment) + binding!!.etNewLatitude.setText(if (upload!!.lat != null) upload!!.lat.toString() else "") + binding!!.etNewLongitude.setText(if (upload!!.lon != null) upload!!.lon.toString() else "") + val selected = upload!!.problemType!!.ordinal + 1 + binding!!.problemType.setSelection(selected) if (station == null) { - station = baseApplication.getDbAdapter().getStationForUpload(upload); + station = baseApplication.getDbAdapter().getStationForUpload(upload) } - - fetchUploadStatus(upload); + fetchUploadStatus(upload) } - if (station != null) { - binding.tvStationTitle.setText(station.getTitle()); - binding.etNewTitle.setText(station.getTitle()); - binding.etNewLatitude.setText(String.valueOf(station.getLat())); - binding.etNewLongitude.setText(String.valueOf(station.getLon())); + binding!!.tvStationTitle.text = station!!.title + binding!!.etNewTitle.setText(station!!.title) + binding!!.etNewLatitude.setText(station!!.lat.toString()) + binding!!.etNewLongitude.setText(station!!.lon.toString()) } } } - public void reportProblem(View view) { - int selectedType = binding.problemType.getSelectedItemPosition(); + fun reportProblem(view: View?) { + val selectedType = binding!!.problemType.selectedItemPosition if (selectedType == 0) { - Toast.makeText(getApplicationContext(), getString(R.string.problem_please_specify), Toast.LENGTH_LONG).show(); - return; + Toast.makeText( + applicationContext, + getString(R.string.problem_please_specify), + Toast.LENGTH_LONG + ).show() + return } - var type = ProblemType.values()[selectedType - 1]; - var comment = binding.etProblemComment.getText().toString(); + val type = ProblemType.values()[selectedType - 1] + val comment = binding!!.etProblemComment.text.toString() if (StringUtils.isBlank(comment)) { - Toast.makeText(getApplicationContext(), getString(R.string.problem_please_comment), Toast.LENGTH_LONG).show(); - return; + Toast.makeText( + applicationContext, + getString(R.string.problem_please_comment), + Toast.LENGTH_LONG + ).show() + return } - - Double lat = null; - Double lon = null; - if (binding.etNewLatitude.getVisibility() == View.VISIBLE) { - lat = parseDouble(binding.etNewLatitude); + var lat: Double? = null + var lon: Double? = null + if (binding!!.etNewLatitude.visibility == View.VISIBLE) { + lat = parseDouble(binding!!.etNewLatitude) } - if (binding.etNewLongitude.getVisibility() == View.VISIBLE) { - lon = parseDouble(binding.etNewLongitude); + if (binding!!.etNewLongitude.visibility == View.VISIBLE) { + lon = parseDouble(binding!!.etNewLongitude) } - if (type == ProblemType.WRONG_LOCATION && (lat == null || lon == null)) { - Toast.makeText(getApplicationContext(), getString(R.string.problem_wrong_lat_lon), Toast.LENGTH_LONG).show(); - return; + if (type === ProblemType.WRONG_LOCATION && (lat == null || lon == null)) { + Toast.makeText( + applicationContext, + getString(R.string.problem_wrong_lat_lon), + Toast.LENGTH_LONG + ).show() + return } - var title = binding.etNewTitle.getText().toString(); - if (type == ProblemType.WRONG_NAME && (StringUtils.isBlank(title) || Objects.equals(station.getTitle(), title))) { - Toast.makeText(getApplicationContext(), getString(R.string.problem_please_provide_corrected_title), Toast.LENGTH_LONG).show(); - return; + val title = binding!!.etNewTitle.text.toString() + if (type === ProblemType.WRONG_NAME && (StringUtils.isBlank(title) || station!!.title == title)) { + Toast.makeText( + applicationContext, + getString(R.string.problem_please_provide_corrected_title), + Toast.LENGTH_LONG + ).show() + return } - - upload = new Upload( - null, - station.getCountry(), - station.getId(), - null, - title, - lat, - lon, - comment, - null, - type - ); - - upload = baseApplication.getDbAdapter().insertUpload(upload); - - var problemReport = new ProblemReport( - station.getCountry(), - station.getId(), - comment, - type, - photoId, - lat, - lon, - title); - - SimpleDialogs.confirmOkCancel(ProblemReportActivity.this, R.string.send_problem_report, - (dialog, which) -> rsapiClient.reportProblem(problemReport).enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - InboxResponse inboxResponse; - if (response.isSuccessful()) { - inboxResponse = response.body(); + upload = Upload( + null, + station!!.country, + station!!.id, + null, + title, + lat, + lon, + comment, + null, + type + ) + upload = baseApplication.getDbAdapter().insertUpload(upload) + val problemReport = ProblemReport( + station!!.country, + station!!.id, + comment, + type, + photoId, + lat, + lon, + title + ) + SimpleDialogs.confirmOkCancel( + this@ProblemReportActivity, R.string.send_problem_report + ) { dialog: DialogInterface?, which: Int -> + rsapiClient!!.reportProblem(problemReport)!! + .enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + val inboxResponse: InboxResponse? + inboxResponse = if (response.isSuccessful) { + response.body() } else if (response.code() == 401) { - onUnauthorized(); - return; + onUnauthorized() + return@confirmOkCancel } else { - inboxResponse = new Gson().fromJson(response.errorBody().charStream(), InboxResponse.class); + Gson().fromJson( + response.errorBody()!!.charStream(), + InboxResponse::class.java + ) } - - upload.setRemoteId(inboxResponse.getId()); - upload.setUploadState(inboxResponse.getState().getUploadState()); - baseApplication.getDbAdapter().updateUpload(upload); - if (inboxResponse.getState() == InboxResponse.InboxResponseState.ERROR) { - SimpleDialogs.confirmOk(ProblemReportActivity.this, - getString(InboxResponse.InboxResponseState.ERROR.getMessageId(), inboxResponse.getMessage())); + upload!!.remoteId = inboxResponse!!.id + upload!!.uploadState = inboxResponse.state.uploadState + baseApplication.getDbAdapter().updateUpload(upload) + if (inboxResponse.state === InboxResponse.InboxResponseState.ERROR) { + confirmOk( + this@ProblemReportActivity, + getString( + InboxResponse.InboxResponseState.ERROR.messageId, + inboxResponse.message + ) + ) } else { - SimpleDialogs.confirmOk(ProblemReportActivity.this, inboxResponse.getState().getMessageId()); + confirmOk(this@ProblemReportActivity, inboxResponse.state.messageId) } } - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Log.e(TAG, "Error reporting problem", t); + override fun onFailure(call: Call, t: Throwable) { + Log.e(TAG, "Error reporting problem", t) } - })); + }) + } } - private void onUnauthorized() { - baseApplication.setAccessToken(null); - rsapiClient.clearToken(); - Toast.makeText(ProblemReportActivity.this, R.string.authorization_failed, Toast.LENGTH_LONG).show(); - startActivity(new Intent(ProblemReportActivity.this, MyDataActivity.class)); - finish(); + private fun onUnauthorized() { + baseApplication.setAccessToken(null) + rsapiClient!!.clearToken() + Toast.makeText(this@ProblemReportActivity, R.string.authorization_failed, Toast.LENGTH_LONG) + .show() + startActivity(Intent(this@ProblemReportActivity, MyDataActivity::class.java)) + finish() } - private Double parseDouble(final EditText editText) { + private fun parseDouble(editText: EditText): Double? { try { - return Double.parseDouble(String.valueOf(editText.getText())); - } catch (Exception e) { - Log.e(TAG, "error parsing double " + editText.getText(), e); + return editText.text.toString().toDouble() + } catch (e: Exception) { + Log.e(TAG, "error parsing double " + editText.text, e) } - return null; + return null } - private void fetchUploadStatus(Upload upload) { - if (upload == null || upload.getRemoteId() == null) { - return; + private fun fetchUploadStatus(upload: Upload?) { + if (upload == null || upload.remoteId == null) { + return } - - var stateQuery = new InboxStateQuery( - upload.getRemoteId(), - upload.getCountry(), - upload.getStationId()); - - rsapiClient.queryUploadState(List.of(stateQuery)).enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call> call, @NonNull Response> response) { - if (response.isSuccessful()) { - var stateQueries = response.body(); - if (stateQueries != null && !stateQueries.isEmpty()) { - var stateQuery = stateQueries.get(0); - binding.uploadStatus.setText(getString(R.string.upload_state, getString(stateQuery.getState().getTextId()))); - binding.uploadStatus.setTextColor(getResources().getColor(stateQuery.getState().getColorId(), null)); - upload.setUploadState(stateQuery.getState()); - upload.setRejectReason(stateQuery.getRejectedReason()); - upload.setCrc32(stateQuery.getCrc32()); - upload.setRemoteId(stateQuery.getId()); - baseApplication.getDbAdapter().updateUpload(upload); + val stateQuery = InboxStateQuery( + upload.remoteId, + upload.country, + upload.stationId + ) + rsapiClient!!.queryUploadState(java.util.List.of(stateQuery))!! + .enqueue(object : Callback?> { + override fun onResponse( + call: Call?>, + response: Response?> + ) { + if (response.isSuccessful) { + val stateQueries = response.body() + if (stateQueries != null && !stateQueries.isEmpty()) { + val stateQuery = stateQueries[0] + binding!!.uploadStatus.text = + getString(R.string.upload_state, getString(stateQuery.state.textId)) + binding!!.uploadStatus.setTextColor( + resources.getColor( + stateQuery.state.colorId, + null + ) + ) + upload.uploadState = stateQuery.state + upload.rejectReason = stateQuery.rejectedReason + upload.crc32 = stateQuery.crc32 + upload.remoteId = stateQuery.id + baseApplication.getDbAdapter().updateUpload(upload) + } + } else if (response.code() == 401) { + onUnauthorized() + } else { + Log.w(TAG, "Upload states not processable") } - } else if (response.code() == 401) { - onUnauthorized(); - } else { - Log.w(TAG, "Upload states not processable"); } - } - - @Override - public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - Log.e(TAG, "Error retrieving upload state", t); - } - }); + override fun onFailure(call: Call?>, t: Throwable) { + Log.e(TAG, "Error retrieving upload state", t) + } + }) } - @Override - public void onBackPressed() { - navigateUp(); + override fun onBackPressed() { + navigateUp() } - public void navigateUp() { - var callingActivity = getCallingActivity(); // if MapsActivity was calling, then we don't want to rebuild the Backstack + fun navigateUp() { + val callingActivity = + callingActivity // if MapsActivity was calling, then we don't want to rebuild the Backstack if (callingActivity == null) { - var upIntent = NavUtils.getParentActivityIntent(this); - assert upIntent != null; - upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - if (NavUtils.shouldUpRecreateTask(this, upIntent) || isTaskRoot()) { - Log.v(TAG, "Recreate back stack"); - TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent).startActivities(); + val upIntent = NavUtils.getParentActivityIntent(this)!! + upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + if (NavUtils.shouldUpRecreateTask(this, upIntent) || isTaskRoot) { + Log.v(TAG, "Recreate back stack") + TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent) + .startActivities() } } - - finish(); + finish() } -} + companion object { + private val TAG = ProblemReportActivity::class.java.simpleName + const val EXTRA_UPLOAD = "EXTRA_UPLOAD" + const val EXTRA_STATION = "EXTRA_STATION" + const val EXTRA_PHOTO_ID = "EXTRA_PHOTO_ID" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/ShowErrorActivity.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/ShowErrorActivity.kt index 9f688109..d87f14f6 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/ShowErrorActivity.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/ShowErrorActivity.kt @@ -1,85 +1,80 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; - -import androidx.appcompat.app.AppCompatActivity; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityShowErrorBinding; - -public class ShowErrorActivity extends AppCompatActivity { - - public static final String EXTRA_ERROR_TEXT = "error"; - - private ActivityShowErrorBinding binding; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - binding = ActivityShowErrorBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - binding.textViewError.setText(getIntent().getStringExtra(EXTRA_ERROR_TEXT)); - - setSupportActionBar(binding.mapsToolbar); - if (getSupportActionBar() != null) { - getSupportActionBar().setTitle(createErrorTitle()); +package de.bahnhoefe.deutschlands.bahnhofsfotos + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import androidx.appcompat.app.AppCompatActivity +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityShowErrorBinding +import java.io.UnsupportedEncodingException +import java.net.URLEncoder +import java.nio.charset.StandardCharsets + +class ShowErrorActivity : AppCompatActivity() { + private var binding: ActivityShowErrorBinding? = null + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityShowErrorBinding.inflate( + layoutInflater + ) + setContentView(binding!!.root) + binding!!.textViewError.text = intent.getStringExtra(EXTRA_ERROR_TEXT) + setSupportActionBar(binding!!.mapsToolbar) + if (supportActionBar != null) { + supportActionBar!!.title = createErrorTitle() } } - private String createErrorTitle() { - return String.format(getString(R.string.error_crash_title), getString(R.string.app_name)); + private fun createErrorTitle(): String { + return String.format(getString(R.string.error_crash_title), getString(R.string.app_name)) } - private void reportBug() { - Uri uriUrl; - try { - uriUrl = Uri.parse( - String.format( - getString(R.string.report_issue_link), - URLEncoder.encode(binding.textViewError.getText().toString(), StandardCharsets.UTF_8.toString()) + private fun reportBug() { + val uriUrl: Uri + uriUrl = try { + Uri.parse( + String.format( + getString(R.string.report_issue_link), + URLEncoder.encode( + binding!!.textViewError.text.toString(), + StandardCharsets.UTF_8.toString() ) - ); - } catch (UnsupportedEncodingException ignored) { + ) + ) + } catch (ignored: UnsupportedEncodingException) { // can't happen as UTF-8 is always available - return; + return } - var intent = new Intent(Intent.ACTION_VIEW, uriUrl); - startActivity(intent); + val intent = Intent(Intent.ACTION_VIEW, uriUrl) + startActivity(intent) } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.show_error, menu); - return super.onCreateOptionsMenu(menu); + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.show_error, menu) + return super.onCreateOptionsMenu(menu) } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.error_share) { - onClickedShare(); - return true; - } else if (item.getItemId() == R.id.error_report) { - reportBug(); - return true; + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.error_share) { + onClickedShare() + return true + } else if (item.itemId == R.id.error_report) { + reportBug() + return true } - return super.onOptionsItemSelected(item); + return super.onOptionsItemSelected(item) } - private void onClickedShare() { - var intent = new Intent(Intent.ACTION_SEND); - intent.putExtra(Intent.EXTRA_SUBJECT, createErrorTitle()); - intent.putExtra(Intent.EXTRA_TEXT, binding.textViewError.getText()); - intent.setType("text/plain"); - startActivity(intent); + private fun onClickedShare() { + val intent = Intent(Intent.ACTION_SEND) + intent.putExtra(Intent.EXTRA_SUBJECT, createErrorTitle()) + intent.putExtra(Intent.EXTRA_TEXT, binding!!.textViewError.text) + intent.type = "text/plain" + startActivity(intent) } -} + companion object { + const val EXTRA_ERROR_TEXT = "error" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/UploadActivity.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/UploadActivity.kt index 442f30db..30cf80fa 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/UploadActivity.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/UploadActivity.kt @@ -1,343 +1,347 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos; - -import static android.content.Intent.createChooser; - -import android.app.Activity; -import android.app.TaskStackBuilder; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.os.Bundle; -import android.provider.MediaStore; -import android.text.Html; -import android.text.TextUtils; -import android.text.method.LinkMovementMethod; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Toast; - -import androidx.activity.OnBackPressedCallback; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.app.ActivityCompat; -import androidx.core.app.NavUtils; -import androidx.core.content.FileProvider; - -import com.google.gson.Gson; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLConnection; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.zip.CRC32; -import java.util.zip.CheckedInputStream; -import java.util.zip.CheckedOutputStream; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityUploadBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.SimpleDialogs; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxResponse; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxStateQuery; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Upload; -import de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi.RSAPIClient; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.FileUtils; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.KeyValueSpinnerItem; -import okhttp3.MediaType; -import okhttp3.RequestBody; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class UploadActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback { - - private static final String TAG = UploadActivity.class.getSimpleName(); - - // Names of Extras that this class reacts to - public static final String EXTRA_UPLOAD = "EXTRA_UPLOAD"; - public static final String EXTRA_STATION = "EXTRA_STATION"; - public static final String EXTRA_LATITUDE = "EXTRA_LATITUDE"; - public static final String EXTRA_LONGITUDE = "EXTRA_LONGITUDE"; - - private BaseApplication baseApplication; - private RSAPIClient rsapiClient; - - private ActivityUploadBinding binding; - - private Upload upload; - private Station station; - private List countries; - private Double latitude; - private Double longitude; - private String bahnhofId; - private Long crc32 = null; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ActivityUploadBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - baseApplication = (BaseApplication) getApplication(); - rsapiClient = baseApplication.getRsapiClient(); - countries = new ArrayList<>(baseApplication.getDbAdapter().getAllCountries()); - - Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); - - if (!baseApplication.isLoggedIn()) { - Toast.makeText(this, R.string.please_login, Toast.LENGTH_LONG).show(); - startActivity(new Intent(this, MyDataActivity.class)); - finish(); - return; +package de.bahnhoefe.deutschlands.bahnhofsfotos + +import android.app.TaskStackBuilder +import android.content.ActivityNotFoundException +import android.content.DialogInterface +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Color +import android.net.Uri +import android.os.Bundle +import android.provider.MediaStore +import android.text.Html +import android.text.TextUtils +import android.text.method.LinkMovementMethod +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Toast +import androidx.activity.OnBackPressedCallback +import androidx.activity.OnBackPressedDispatcher.addCallback +import androidx.activity.result.ActivityResult +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback +import androidx.core.app.NavUtils +import androidx.core.content.FileProvider +import com.google.gson.Gson +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ActivityUploadBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs.SimpleDialogs +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country.Companion.getCountryByCode +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxResponse +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxStateQuery +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Upload +import de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi.RSAPIClient +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.FileUtils +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.KeyValueSpinnerItem +import okhttp3.RequestBody +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.IOException +import java.io.UnsupportedEncodingException +import java.net.URLConnection +import java.net.URLEncoder +import java.nio.charset.StandardCharsets +import java.util.Objects +import java.util.zip.CRC32 +import java.util.zip.CheckedInputStream +import java.util.zip.CheckedOutputStream + +class UploadActivity : AppCompatActivity(), OnRequestPermissionsResultCallback { + private var baseApplication: BaseApplication? = null + private var rsapiClient: RSAPIClient? = null + private var binding: ActivityUploadBinding? = null + private var upload: Upload? = null + private var station: Station? = null + private var countries: List? = null + private var latitude: Double? = null + private var longitude: Double? = null + private var bahnhofId: String? = null + private var crc32: Long? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityUploadBinding.inflate( + layoutInflater + ) + setContentView(binding!!.root) + baseApplication = application as BaseApplication + rsapiClient = baseApplication.getRsapiClient() + countries = ArrayList(baseApplication.getDbAdapter().allCountries) + Objects.requireNonNull(supportActionBar).setDisplayHomeAsUpEnabled(true) + if (!baseApplication!!.isLoggedIn) { + Toast.makeText(this, R.string.please_login, Toast.LENGTH_LONG).show() + startActivity(Intent(this, MyDataActivity::class.java)) + finish() + return } - if (!baseApplication.getProfile().isAllowedToUploadPhoto()) { - SimpleDialogs.confirmOk(this, R.string.no_photo_upload_allowed, (dialog, which) -> { - startActivity(new Intent(this, MyDataActivity.class)); - finish(); - }); - return; + SimpleDialogs.confirmOk( + this, + R.string.no_photo_upload_allowed + ) { dialog: DialogInterface?, which: Int -> + startActivity(Intent(this, MyDataActivity::class.java)) + finish() + } + return } - - getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - navigateUp(); + getOnBackPressedDispatcher().addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + navigateUp() } - }); - - onNewIntent(getIntent()); + }) + onNewIntent(intent) } - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) if (intent != null) { - upload = (Upload) intent.getSerializableExtra(EXTRA_UPLOAD); - station = (Station) intent.getSerializableExtra(EXTRA_STATION); - latitude = (Double) intent.getSerializableExtra(EXTRA_LATITUDE); - longitude = (Double) intent.getSerializableExtra(EXTRA_LONGITUDE); - - if (station == null && upload != null && upload.isUploadForExistingStation()) { - station = baseApplication.getDbAdapter().getStationForUpload(upload); + upload = intent.getSerializableExtra(EXTRA_UPLOAD) as Upload? + station = intent.getSerializableExtra(EXTRA_STATION) as Station? + latitude = intent.getSerializableExtra(EXTRA_LATITUDE) as Double? + longitude = intent.getSerializableExtra(EXTRA_LONGITUDE) as Double? + if (station == null && upload != null && upload!!.isUploadForExistingStation) { + station = baseApplication.getDbAdapter().getStationForUpload(upload) } - - if (latitude == null && longitude == null && upload != null && upload.isUploadForMissingStation()) { - latitude = upload.getLat(); - longitude = upload.getLon(); + if (latitude == null && longitude == null && upload != null && upload!!.isUploadForMissingStation) { + latitude = upload!!.lat + longitude = upload!!.lon } - if (station == null && (latitude == null || longitude == null)) { - Log.w(TAG, "EXTRA_STATION and EXTRA_LATITUDE or EXTRA_LONGITUDE in intent data missing"); - Toast.makeText(this, R.string.station_or_coords_not_found, Toast.LENGTH_LONG).show(); - finish(); - return; + Log.w( + TAG, + "EXTRA_STATION and EXTRA_LATITUDE or EXTRA_LONGITUDE in intent data missing" + ) + Toast.makeText(this, R.string.station_or_coords_not_found, Toast.LENGTH_LONG).show() + finish() + return } - if (station != null) { - bahnhofId = station.getId(); - binding.upload.etStationTitle.setText(station.getTitle()); - binding.upload.etStationTitle.setInputType(EditorInfo.TYPE_NULL); - binding.upload.etStationTitle.setSingleLine(false); - + bahnhofId = station!!.id + binding!!.upload.etStationTitle.setText(station!!.title) + binding!!.upload.etStationTitle.inputType = EditorInfo.TYPE_NULL + binding!!.upload.etStationTitle.isSingleLine = false if (upload == null) { - upload = baseApplication.getDbAdapter().getPendingUploadsForStation(station).stream() + upload = + baseApplication.getDbAdapter().getPendingUploadsForStation(station).stream() .filter(Upload::isPendingPhotoUpload) .findFirst() - .orElse(null); + .orElse(null) } - - setLocalBitmap(upload); - - binding.upload.spActive.setVisibility(View.GONE); - binding.upload.spCountries.setVisibility(View.GONE); - - String country = station.getCountry(); - updateOverrideLicense(country); + setLocalBitmap(upload) + binding!!.upload.spActive.visibility = View.GONE + binding!!.upload.spCountries.visibility = View.GONE + val country = station!!.country + updateOverrideLicense(country) } else { if (upload == null) { - upload = baseApplication.getDbAdapter().getPendingUploadForCoordinates(latitude, longitude); + upload = baseApplication.getDbAdapter() + .getPendingUploadForCoordinates(latitude, longitude) } - - binding.upload.etStationTitle.setInputType(EditorInfo.TYPE_CLASS_TEXT); - binding.upload.spActive.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, getResources().getStringArray(R.array.active_flag_options))); + binding!!.upload.etStationTitle.inputType = EditorInfo.TYPE_CLASS_TEXT + binding!!.upload.spActive.adapter = ArrayAdapter( + this, android.R.layout.simple_spinner_dropdown_item, resources.getStringArray( + R.array.active_flag_options + ) + ) if (upload != null) { - binding.upload.etStationTitle.setText(upload.getTitle()); - setLocalBitmap(upload); - - if (upload.getActive() == null) { - binding.upload.spActive.setSelection(0); - } else if (upload.getActive()) { - binding.upload.spActive.setSelection(1); + binding!!.upload.etStationTitle.setText(upload!!.title) + setLocalBitmap(upload) + if (upload!!.active == null) { + binding!!.upload.spActive.setSelection(0) + } else if (upload!!.active!!) { + binding!!.upload.spActive.setSelection(1) } else { - binding.upload.spActive.setSelection(2); + binding!!.upload.spActive.setSelection(2) } } else { - binding.upload.spActive.setSelection(0); + binding!!.upload.spActive.setSelection(0) } - - var items = new KeyValueSpinnerItem[countries.size() + 1]; - items[0] = new KeyValueSpinnerItem(getString(R.string.chooseCountry), ""); - int selected = 0; - - for (int i = 0; i < countries.size(); i++) { - var country = countries.get(i); - items[i + 1] = new KeyValueSpinnerItem(country.getName(), country.getCode()); - if (upload != null && country.getCode().equals(upload.getCountry())) { - selected = i + 1; + val items = arrayOfNulls( + countries!!.size + 1 + ) + items[0] = KeyValueSpinnerItem(getString(R.string.chooseCountry), "") + var selected = 0 + for (i in countries!!.indices) { + val country = countries!![i] + items[i + 1] = KeyValueSpinnerItem(country!!.name, country.code) + if (upload != null && country.code == upload!!.country) { + selected = i + 1 } } - var countryAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, items); - countryAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - binding.upload.spCountries.setAdapter(countryAdapter); - binding.upload.spCountries.setSelection(selected); - binding.upload.spCountries.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(final AdapterView parent, final View view, final int position, final long id) { - var selectedCountry = (KeyValueSpinnerItem) parent.getItemAtPosition(position); - updateOverrideLicense(selectedCountry.getValue()); - } + val countryAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, items) + countryAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding!!.upload.spCountries.adapter = countryAdapter + binding!!.upload.spCountries.setSelection(selected) + binding!!.upload.spCountries.onItemSelectedListener = + object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>, + view: View, + position: Int, + id: Long + ) { + val selectedCountry = + parent.getItemAtPosition(position) as KeyValueSpinnerItem + updateOverrideLicense(selectedCountry.value) + } - @Override - public void onNothingSelected(final AdapterView parent) { - updateOverrideLicense(null); + override fun onNothingSelected(parent: AdapterView<*>?) { + updateOverrideLicense(null) + } } - }); } - if (upload != null) { - binding.upload.etComment.setText(upload.getComment()); + binding!!.upload.etComment.setText(upload!!.comment) } - - binding.upload.txtPanorama.setText(Html.fromHtml(getString(R.string.panorama_info), Html.FROM_HTML_MODE_COMPACT)); - binding.upload.txtPanorama.setMovementMethod(LinkMovementMethod.getInstance()); - binding.upload.txtPanorama.setLinkTextColor(Color.parseColor("#c71c4d")); - + binding!!.upload.txtPanorama.text = + Html.fromHtml(getString(R.string.panorama_info), Html.FROM_HTML_MODE_COMPACT) + binding!!.upload.txtPanorama.movementMethod = LinkMovementMethod.getInstance() + binding!!.upload.txtPanorama.setLinkTextColor(Color.parseColor("#c71c4d")) } } - private void updateOverrideLicense(final String country) { - var overrideLicense = Country.getCountryByCode(countries, country).map(Country::getOverrideLicense).orElse(null); + private fun updateOverrideLicense(country: String?) { + val overrideLicense = + getCountryByCode(countries, country).map(Country::overrideLicense).orElse(null) if (overrideLicense != null) { - binding.upload.cbSpecialLicense.setText(getString(R.string.special_license, overrideLicense)); + binding!!.upload.cbSpecialLicense.text = + getString(R.string.special_license, overrideLicense) } - binding.upload.cbSpecialLicense.setVisibility(overrideLicense == null ? View.GONE : View.VISIBLE); + binding!!.upload.cbSpecialLicense.visibility = + if (overrideLicense == null) View.GONE else View.VISIBLE } - private final ActivityResultLauncher imageCaptureResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == Activity.RESULT_OK) { - try { - assertCurrentPhotoUploadExists(); - var cameraTempFile = getCameraTempFile(); - var options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; // just query the image size in the first step - BitmapFactory.decodeFile(cameraTempFile.getPath(), options); - - int sampling = options.outWidth / Constants.STORED_PHOTO_WIDTH; - if (sampling > 1) { - options.inSampleSize = sampling; - } - options.inJustDecodeBounds = false; - - storeBitmapToLocalFile(getStoredMediaFile(upload), BitmapFactory.decodeFile(cameraTempFile.getPath(), options)); - FileUtils.deleteQuietly(cameraTempFile); - } catch (Exception e) { - Log.e(TAG, "Error processing photo", e); - Toast.makeText(getApplicationContext(), getString(R.string.error_processing_photo) + e.getMessage(), Toast.LENGTH_LONG).show(); - } + private val imageCaptureResultLauncher = registerForActivityResult( + StartActivityForResult() + ) { result: ActivityResult -> + if (result.resultCode == RESULT_OK) { + try { + assertCurrentPhotoUploadExists() + val cameraTempFile = cameraTempFile + val options = BitmapFactory.Options() + options.inJustDecodeBounds = true // just query the image size in the first step + BitmapFactory.decodeFile(cameraTempFile!!.path, options) + val sampling = options.outWidth / Constants.STORED_PHOTO_WIDTH + if (sampling > 1) { + options.inSampleSize = sampling } - }); - - public void takePicture(View view) { - if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) { - return; + options.inJustDecodeBounds = false + storeBitmapToLocalFile( + getStoredMediaFile(upload), BitmapFactory.decodeFile( + cameraTempFile.path, options + ) + ) + FileUtils.deleteQuietly(cameraTempFile) + } catch (e: Exception) { + Log.e(TAG, "Error processing photo", e) + Toast.makeText( + applicationContext, + getString(R.string.error_processing_photo) + e.message, + Toast.LENGTH_LONG + ).show() + } } + } - assertCurrentPhotoUploadExists(); - var photoURI = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".fileprovider", getCameraTempFile()); - var intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); - intent.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, getResources().getString(R.string.app_name)); - intent.putExtra(MediaStore.EXTRA_MEDIA_TITLE, binding.upload.etStationTitle.getText()); - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + fun takePicture(view: View?) { + if (!packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) { + return + } + assertCurrentPhotoUploadExists() + val photoURI = FileProvider.getUriForFile( + this, + BuildConfig.APPLICATION_ID + ".fileprovider", + cameraTempFile!! + ) + val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) + intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI) + intent.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, resources.getString(R.string.app_name)) + intent.putExtra(MediaStore.EXTRA_MEDIA_TITLE, binding!!.upload.etStationTitle.text) + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) try { - imageCaptureResultLauncher.launch(intent); - } catch (ActivityNotFoundException exception) { - Toast.makeText(this, R.string.no_image_capture_app_found, Toast.LENGTH_LONG).show(); + imageCaptureResultLauncher.launch(intent) + } catch (exception: ActivityNotFoundException) { + Toast.makeText(this, R.string.no_image_capture_app_found, Toast.LENGTH_LONG).show() } } - private final ActivityResultLauncher selectPictureResultLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), - uri -> { - try (var parcelFileDescriptor = getContentResolver().openFileDescriptor(uri, "r")) { - if (parcelFileDescriptor == null) { - return; - } - var fileDescriptor = parcelFileDescriptor.getFileDescriptor(); - assertCurrentPhotoUploadExists(); - - var bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor); - int sampling = bitmap.getWidth() / Constants.STORED_PHOTO_WIDTH; - var scaledScreen = bitmap; - if (sampling > 1) { - scaledScreen = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth() / sampling, bitmap.getHeight() / sampling, false); - } - - storeBitmapToLocalFile(getStoredMediaFile(upload), scaledScreen); - } catch (Exception e) { - Log.e(TAG, "Error processing photo", e); - Toast.makeText(getApplicationContext(), getString(R.string.error_processing_photo) + e.getMessage(), Toast.LENGTH_LONG).show(); + private val selectPictureResultLauncher = registerForActivityResult( + GetContent() + ) { uri: Uri? -> + try { + contentResolver.openFileDescriptor(uri!!, "r").use { parcelFileDescriptor -> + if (parcelFileDescriptor == null) { + return@registerForActivityResult } - }); + val fileDescriptor = parcelFileDescriptor.fileDescriptor + assertCurrentPhotoUploadExists() + val bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor) + val sampling = bitmap.width / Constants.STORED_PHOTO_WIDTH + var scaledScreen = bitmap + if (sampling > 1) { + scaledScreen = Bitmap.createScaledBitmap( + bitmap, + bitmap.width / sampling, + bitmap.height / sampling, + false + ) + } + storeBitmapToLocalFile(getStoredMediaFile(upload), scaledScreen) + } + } catch (e: Exception) { + Log.e(TAG, "Error processing photo", e) + Toast.makeText( + applicationContext, + getString(R.string.error_processing_photo) + e.message, + Toast.LENGTH_LONG + ).show() + } + } - public void selectPicture(View view) { - selectPictureResultLauncher.launch("image/*"); + fun selectPicture(view: View?) { + selectPictureResultLauncher.launch("image/*") } - private void assertCurrentPhotoUploadExists() { - if (upload == null || upload.isProblemReport() || upload.isUploaded()) { - upload = new Upload( - null, - station != null ? station.getCountry() : null, - station != null ? station.getId() : null, - null, - null, - latitude, - longitude); - upload = baseApplication.getDbAdapter().insertUpload(upload); + private fun assertCurrentPhotoUploadExists() { + if (upload == null || upload!!.isProblemReport || upload!!.isUploaded) { + upload = Upload( + null, + if (station != null) station!!.country else null, + if (station != null) station!!.id else null, + null, + null, + latitude, + longitude + ) + upload = baseApplication.getDbAdapter().insertUpload(upload) } } - private void storeBitmapToLocalFile(File file, Bitmap bitmap) throws IOException { + @Throws(IOException::class) + private fun storeBitmapToLocalFile(file: File?, bitmap: Bitmap?) { if (bitmap == null) { - throw new RuntimeException(getString(R.string.error_scaling_photo)); + throw RuntimeException(getString(R.string.error_scaling_photo)) } - Log.i(TAG, "Save photo with width=" + bitmap.getWidth() + " and height=" + bitmap.getHeight() + " to: " + file); - try (var cos = new CheckedOutputStream(new FileOutputStream(file), new CRC32())) { - bitmap.compress(Bitmap.CompressFormat.JPEG, Constants.STORED_PHOTO_QUALITY, cos); - crc32 = cos.getChecksum().getValue(); - setLocalBitmap(upload); + Log.i( + TAG, + "Save photo with width=" + bitmap.width + " and height=" + bitmap.height + " to: " + file + ) + CheckedOutputStream(FileOutputStream(file), CRC32()).use { cos -> + bitmap.compress(Bitmap.CompressFormat.JPEG, Constants.STORED_PHOTO_QUALITY, cos) + crc32 = cos.checksum.value + setLocalBitmap(upload) } } @@ -346,257 +350,290 @@ public class UploadActivity extends AppCompatActivity implements ActivityCompat. * * @return the File */ - @Nullable - public File getStoredMediaFile(Upload upload) { - if (upload == null) { - return null; - } - return FileUtils.getStoredMediaFile(this, upload.getId()); + fun getStoredMediaFile(upload: Upload?): File? { + return if (upload == null) { + null + } else FileUtils.getStoredMediaFile(this, upload.id) } - /** - * Get the file path for the Camera app to store the unprocessed photo to. - */ - private File getCameraTempFile() { - return FileUtils.getImageCacheFile(this, String.valueOf(upload.getId())); - } + private val cameraTempFile: File? + /** + * Get the file path for the Camera app to store the unprocessed photo to. + */ + private get() = FileUtils.getImageCacheFile(this, upload!!.id.toString()) - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.upload, menu); - return super.onCreateOptionsMenu(menu); + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.upload, menu) + return super.onCreateOptionsMenu(menu) } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int itemId = item.getItemId(); + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val itemId = item.itemId if (itemId == R.id.share_photo) { - var shareIntent = createPhotoSendIntent(); + val shareIntent = createPhotoSendIntent() if (shareIntent != null) { - shareIntent.putExtra(Intent.EXTRA_TEXT, binding.upload.etStationTitle.getText()); - shareIntent.setType("image/jpeg"); - startActivity(createChooser(shareIntent, getString(R.string.share_photo))); + shareIntent.putExtra(Intent.EXTRA_TEXT, binding!!.upload.etStationTitle.text) + shareIntent.type = "image/jpeg" + startActivity(Intent.createChooser(shareIntent, getString(R.string.share_photo))) } } else if (itemId == android.R.id.home) { - navigateUp(); + navigateUp() } else { - return super.onOptionsItemSelected(item); + return super.onOptionsItemSelected(item) } - - return true; + return true } - public void navigateUp() { - var callingActivity = getCallingActivity(); // if MapsActivity was calling, then we don't want to rebuild the Backstack - var upIntent = NavUtils.getParentActivityIntent(this); + fun navigateUp() { + val callingActivity = + callingActivity // if MapsActivity was calling, then we don't want to rebuild the Backstack + val upIntent = NavUtils.getParentActivityIntent(this) if (callingActivity == null && upIntent != null) { - upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - if (NavUtils.shouldUpRecreateTask(this, upIntent) || isTaskRoot()) { - Log.v(TAG, "Recreate back stack"); - TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent).startActivities(); + upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + if (NavUtils.shouldUpRecreateTask(this, upIntent) || isTaskRoot) { + Log.v(TAG, "Recreate back stack") + TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent) + .startActivities() } } - - finish(); + finish() } - public void upload(View view) { - if (TextUtils.isEmpty(binding.upload.etStationTitle.getText())) { - Toast.makeText(this, R.string.station_title_needed, Toast.LENGTH_LONG).show(); - return; + fun upload(view: View?) { + if (TextUtils.isEmpty(binding!!.upload.etStationTitle.text)) { + Toast.makeText(this, R.string.station_title_needed, Toast.LENGTH_LONG).show() + return } - - assertCurrentPhotoUploadExists(); - - var mediaFile = getStoredMediaFile(upload); - assert mediaFile != null; - + assertCurrentPhotoUploadExists() + val mediaFile = getStoredMediaFile(upload)!! if (!mediaFile.exists()) { if (station != null) { - Toast.makeText(this, R.string.please_take_photo, Toast.LENGTH_LONG).show(); - return; + Toast.makeText(this, R.string.please_take_photo, Toast.LENGTH_LONG).show() + return } } - - if (binding.upload.cbSpecialLicense.getText().length() > 0 && !binding.upload.cbSpecialLicense.isChecked()) { - Toast.makeText(this, R.string.special_license_confirm, Toast.LENGTH_LONG).show(); - return; + if (binding!!.upload.cbSpecialLicense.text.length > 0 && !binding!!.upload.cbSpecialLicense.isChecked) { + Toast.makeText(this, R.string.special_license_confirm, Toast.LENGTH_LONG).show() + return } - if (crc32 != null && upload != null && crc32.equals(upload.getCrc32()) && !binding.upload.cbChecksum.isChecked()) { - Toast.makeText(this, R.string.photo_checksum, Toast.LENGTH_LONG).show(); - return; + if (crc32 != null && upload != null && crc32 == upload!!.crc32 && !binding!!.upload.cbChecksum.isChecked) { + Toast.makeText(this, R.string.photo_checksum, Toast.LENGTH_LONG).show() + return } if (station == null) { - if (binding.upload.spActive.getSelectedItemPosition() == 0) { - Toast.makeText(this, R.string.active_flag_choose, Toast.LENGTH_LONG).show(); - return; + if (binding!!.upload.spActive.selectedItemPosition == 0) { + Toast.makeText(this, R.string.active_flag_choose, Toast.LENGTH_LONG).show() + return } - var selectedCountry = (KeyValueSpinnerItem) binding.upload.spCountries.getSelectedItem(); - upload.setCountry(selectedCountry.getValue()); + val selectedCountry = binding!!.upload.spCountries.selectedItem as KeyValueSpinnerItem + upload!!.country = selectedCountry.value } - - SimpleDialogs.confirmOkCancel(this, station != null ? R.string.photo_upload : R.string.report_missing_station, (dialog, which) -> { - binding.upload.progressBar.setVisibility(View.VISIBLE); - - var stationTitle = binding.upload.etStationTitle.getText().toString(); - var comment = binding.upload.etComment.getText().toString(); - upload.setTitle(stationTitle); - upload.setComment(comment); - + SimpleDialogs.confirmOkCancel( + this, + if (station != null) R.string.photo_upload else R.string.report_missing_station + ) { dialog: DialogInterface?, which: Int -> + binding!!.upload.progressBar.visibility = View.VISIBLE + var stationTitle: String? = binding!!.upload.etStationTitle.text.toString() + var comment: String? = binding!!.upload.etComment.text.toString() + upload!!.title = stationTitle + upload!!.comment = comment try { - stationTitle = URLEncoder.encode(binding.upload.etStationTitle.getText().toString(), String.valueOf(StandardCharsets.UTF_8)); - comment = URLEncoder.encode(comment, String.valueOf(StandardCharsets.UTF_8)); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "Error encoding station title or comment", e); + stationTitle = URLEncoder.encode( + binding!!.upload.etStationTitle.text.toString(), + StandardCharsets.UTF_8.toString() + ) + comment = URLEncoder.encode(comment, StandardCharsets.UTF_8.toString()) + } catch (e: UnsupportedEncodingException) { + Log.e(TAG, "Error encoding station title or comment", e) } - upload.setActive(binding.upload.spActive.getSelectedItemPosition() == 1); - baseApplication.getDbAdapter().updateUpload(upload); - - var file = mediaFile.exists() ? RequestBody.create(mediaFile, MediaType.parse(URLConnection.guessContentTypeFromName(mediaFile.getName()))) : RequestBody.create(new byte[]{}, MediaType.parse("application/octet-stream")); - rsapiClient.photoUpload(bahnhofId, station != null ? station.getCountry() : upload.getCountry(), - stationTitle, latitude, longitude, comment, upload.getActive(), file).enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - binding.upload.progressBar.setVisibility(View.GONE); - InboxResponse inboxResponse; - if (response.isSuccessful()) { - inboxResponse = response.body(); + upload!!.active = binding!!.upload.spActive.selectedItemPosition == 1 + baseApplication.getDbAdapter().updateUpload(upload) + val file: RequestBody = if (mediaFile.exists()) RequestBody.create( + mediaFile, parse.parse( + URLConnection.guessContentTypeFromName( + mediaFile.name + ) + ) + ) else RequestBody.create(byteArrayOf(), parse.parse("application/octet-stream")) + rsapiClient!!.photoUpload( + bahnhofId, if (station != null) station!!.country else upload!!.country, + stationTitle, latitude, longitude, comment, upload!!.active, file + )!!.enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + binding!!.upload.progressBar.visibility = View.GONE + val inboxResponse: InboxResponse? + inboxResponse = if (response.isSuccessful) { + response.body() } else if (response.code() == 401) { - onUnauthorized(); - return; + onUnauthorized() + return@confirmOkCancel } else { - assert response.errorBody() != null; - var gson = new Gson(); - inboxResponse = gson.fromJson(response.errorBody().charStream(), InboxResponse.class); + assert(response.errorBody() != null) + val gson = Gson() + gson.fromJson( + response.errorBody()!!.charStream(), + InboxResponse::class.java + ) } - - assert inboxResponse != null; - upload.setRemoteId(inboxResponse.getId()); - upload.setInboxUrl(inboxResponse.getInboxUrl()); - upload.setUploadState(inboxResponse.getState().getUploadState()); - upload.setCrc32(inboxResponse.getCrc32()); - baseApplication.getDbAdapter().updateUpload(upload); - if (inboxResponse.getState() == InboxResponse.InboxResponseState.ERROR) { - SimpleDialogs.confirmOk(UploadActivity.this, - getString(InboxResponse.InboxResponseState.ERROR.getMessageId(), inboxResponse.getMessage())); + assert(inboxResponse != null) + upload!!.remoteId = inboxResponse!!.id + upload!!.inboxUrl = inboxResponse.inboxUrl + upload!!.uploadState = inboxResponse.state.uploadState + upload!!.crc32 = inboxResponse.crc32 + baseApplication.getDbAdapter().updateUpload(upload) + if (inboxResponse.state === InboxResponse.InboxResponseState.ERROR) { + confirmOk( + this@UploadActivity, + getString( + InboxResponse.InboxResponseState.ERROR.messageId, + inboxResponse.message + ) + ) } else { - SimpleDialogs.confirmOk(UploadActivity.this, inboxResponse.getState().getMessageId()); + confirmOk(this@UploadActivity, inboxResponse.state.messageId) } } - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Log.e(TAG, "Error uploading photo", t); - binding.upload.progressBar.setVisibility(View.GONE); - - SimpleDialogs.confirmOk(UploadActivity.this, - getString(InboxResponse.InboxResponseState.ERROR.getMessageId(), t.getMessage())); - fetchUploadStatus(upload); // try to get the upload state again + override fun onFailure(call: Call, t: Throwable) { + Log.e(TAG, "Error uploading photo", t) + binding!!.upload.progressBar.visibility = View.GONE + confirmOk( + this@UploadActivity, + getString(InboxResponse.InboxResponseState.ERROR.messageId, t.message) + ) + fetchUploadStatus(upload) // try to get the upload state again } - }); - }); + }) + } } - private Intent createPhotoSendIntent() { - var file = getStoredMediaFile(upload); + private fun createPhotoSendIntent(): Intent? { + val file = getStoredMediaFile(upload) if (file != null && file.canRead()) { - var sendIntent = new Intent(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(UploadActivity.this, - BuildConfig.APPLICATION_ID + ".fileprovider", file)); - return sendIntent; + val sendIntent = Intent(Intent.ACTION_SEND) + sendIntent.putExtra( + Intent.EXTRA_STREAM, FileProvider.getUriForFile( + this@UploadActivity, + BuildConfig.APPLICATION_ID + ".fileprovider", file + ) + ) + return sendIntent } - return null; + return null } /** * Fetch bitmap from device local location, if it exists, and set the photo view. */ - private void setLocalBitmap(Upload upload) { - var localPhoto = checkForLocalPhoto(upload); + private fun setLocalBitmap(upload: Upload?) { + val localPhoto = checkForLocalPhoto(upload) if (localPhoto != null) { - binding.upload.imageview.setImageBitmap(localPhoto); - fetchUploadStatus(upload); + binding!!.upload.imageview.setImageBitmap(localPhoto) + fetchUploadStatus(upload) } } - private void fetchUploadStatus(Upload upload) { - if (upload == null || upload.getRemoteId() == null) { - return; + private fun fetchUploadStatus(upload: Upload?) { + if (upload == null || upload.remoteId == null) { + return } - var stateQuery = new InboxStateQuery( - upload.getRemoteId(), - upload.getCountry(), - upload.getStationId()); - - rsapiClient.queryUploadState(List.of(stateQuery)).enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call> call, @NonNull Response> response) { - if (response.isSuccessful()) { - var stateQueries = response.body(); - if (stateQueries != null && !stateQueries.isEmpty()) { - var stateQuery = stateQueries.get(0); - binding.upload.uploadStatus.setText(getString(R.string.upload_state, getString(stateQuery.getState().getTextId()))); - binding.upload.uploadStatus.setTextColor(getResources().getColor(stateQuery.getState().getColorId(), null)); - binding.upload.uploadStatus.setVisibility(View.VISIBLE); - upload.setUploadState(stateQuery.getState()); - upload.setRejectReason(stateQuery.getRejectedReason()); - upload.setCrc32(stateQuery.getCrc32()); - upload.setRemoteId(stateQuery.getId()); - baseApplication.getDbAdapter().updateUpload(upload); - updateCrc32Checkbox(); + val stateQuery = InboxStateQuery( + upload.remoteId, + upload.country, + upload.stationId + ) + rsapiClient!!.queryUploadState(java.util.List.of(stateQuery))!! + .enqueue(object : Callback?> { + override fun onResponse( + call: Call?>, + response: Response?> + ) { + if (response.isSuccessful) { + val stateQueries = response.body() + if (stateQueries != null && !stateQueries.isEmpty()) { + val stateQuery = stateQueries[0] + binding!!.upload.uploadStatus.text = + getString(R.string.upload_state, getString(stateQuery.state.textId)) + binding!!.upload.uploadStatus.setTextColor( + resources.getColor( + stateQuery.state.colorId, + null + ) + ) + binding!!.upload.uploadStatus.visibility = View.VISIBLE + upload.uploadState = stateQuery.state + upload.rejectReason = stateQuery.rejectedReason + upload.crc32 = stateQuery.crc32 + upload.remoteId = stateQuery.id + baseApplication.getDbAdapter().updateUpload(upload) + updateCrc32Checkbox() + } + } else if (response.code() == 401) { + onUnauthorized() + } else { + Log.w(TAG, "Upload states not processable") } - } else if (response.code() == 401) { - onUnauthorized(); - } else { - Log.w(TAG, "Upload states not processable"); } - } - - @Override - public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - Log.e(TAG, "Error retrieving upload state", t); - } - }); + override fun onFailure(call: Call?>, t: Throwable) { + Log.e(TAG, "Error retrieving upload state", t) + } + }) } - private void onUnauthorized() { - baseApplication.setAccessToken(null); - rsapiClient.clearToken(); - Toast.makeText(this, R.string.authorization_failed, Toast.LENGTH_LONG).show(); - startActivity(new Intent(this, MyDataActivity.class)); - finish(); + private fun onUnauthorized() { + baseApplication.setAccessToken(null) + rsapiClient!!.clearToken() + Toast.makeText(this, R.string.authorization_failed, Toast.LENGTH_LONG).show() + startActivity(Intent(this, MyDataActivity::class.java)) + finish() } - private void updateCrc32Checkbox() { + private fun updateCrc32Checkbox() { if (crc32 != null && upload != null) { - var sameChecksum = crc32.equals(upload.getCrc32()); - binding.upload.cbChecksum.setVisibility(sameChecksum ? View.VISIBLE : View.GONE); + val sameChecksum = crc32 == upload!!.crc32 + binding!!.upload.cbChecksum.visibility = if (sameChecksum) View.VISIBLE else View.GONE } } /** * Check if there's a local photo file for this station. */ - @Nullable - private Bitmap checkForLocalPhoto(Upload upload) { + private fun checkForLocalPhoto(upload: Upload?): Bitmap? { // show the image - var localFile = getStoredMediaFile(upload); - Log.d(TAG, "File: " + localFile); - crc32 = null; + val localFile = getStoredMediaFile(upload) + Log.d(TAG, "File: $localFile") + crc32 = null if (localFile != null && localFile.canRead()) { - Log.d(TAG, "FileGetPath: " + localFile.getPath()); - try (var cis = new CheckedInputStream(new FileInputStream(localFile), new CRC32())) { - var scaledScreen = BitmapFactory.decodeStream(cis); - crc32 = cis.getChecksum().getValue(); - Log.d(TAG, "img width " + scaledScreen.getWidth() + ", height " + scaledScreen.getHeight() + ", crc32 " + crc32); - updateCrc32Checkbox(); - return scaledScreen; - } catch (Exception e) { - Log.e(TAG, String.format("Error reading media file for station %s", bahnhofId), e); + Log.d(TAG, "FileGetPath: " + localFile.path) + try { + CheckedInputStream(FileInputStream(localFile), CRC32()).use { cis -> + val scaledScreen = BitmapFactory.decodeStream(cis) + crc32 = cis.checksum.value + Log.d( + TAG, + "img width " + scaledScreen.width + ", height " + scaledScreen.height + ", crc32 " + crc32 + ) + updateCrc32Checkbox() + return scaledScreen + } + } catch (e: Exception) { + Log.e(TAG, String.format("Error reading media file for station %s", bahnhofId), e) } } - return null; + return null } -} + companion object { + private val TAG = UploadActivity::class.java.simpleName + + // Names of Extras that this class reacts to + const val EXTRA_UPLOAD = "EXTRA_UPLOAD" + const val EXTRA_STATION = "EXTRA_STATION" + const val EXTRA_LATITUDE = "EXTRA_LATITUDE" + const val EXTRA_LONGITUDE = "EXTRA_LONGITUDE" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/CountryAdapter.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/CountryAdapter.kt index 21be4562..d1e6f5b5 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/CountryAdapter.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/CountryAdapter.kt @@ -1,123 +1,121 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.db; - -import android.content.Context; -import android.database.Cursor; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.CursorAdapter; - -import java.util.HashSet; -import java.util.Set; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.BaseApplication; -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ItemCountryBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants; - -public class CountryAdapter extends CursorAdapter { - private final LayoutInflater layoutInflater; - private final String TAG = getClass().getSimpleName(); - private final Set selectedCountries; - - private final Context context; - - public CountryAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - selectedCountries = new HashSet<>(BaseApplication.getInstance().getCountryCodes()); - this.context = context; +package de.bahnhoefe.deutschlands.bahnhofsfotos.db + +import android.content.Context +import android.database.Cursor +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CheckBox +import android.widget.CursorAdapter +import de.bahnhoefe.deutschlands.bahnhofsfotos.BaseApplication +import de.bahnhoefe.deutschlands.bahnhofsfotos.R +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ItemCountryBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants.COUNTRIES + +class CountryAdapter(private val context: Context, c: Cursor?, flags: Int) : CursorAdapter( + context, c, flags +) { + private val layoutInflater: LayoutInflater = + context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater + private val selectedCountries: MutableSet = + BaseApplication.instance.countryCodes.toMutableSet() + + companion object { + private val TAG = CountryAdapter::class.java.simpleName } - public void getView(int selectedPosition, View convertView, ViewGroup parent, Cursor cursor) { - ItemCountryBinding binding; - if (convertView == null) { - binding = ItemCountryBinding.inflate(layoutInflater, parent, false); - convertView = binding.getRoot(); - + fun getView(selectedPosition: Int, convertView: View?, parent: ViewGroup?, cursor: Cursor?) { + var rowView = convertView + val binding: ItemCountryBinding + if (rowView == null) { + binding = ItemCountryBinding.inflate(layoutInflater, parent, false) + rowView = binding.root if (selectedPosition % 2 == 1) { - convertView.setBackgroundResource(R.drawable.item_list_backgroundcolor); + rowView.setBackgroundResource(R.drawable.item_list_backgroundcolor) } else { - convertView.setBackgroundResource(R.drawable.item_list_backgroundcolor2); + rowView.setBackgroundResource(R.drawable.item_list_backgroundcolor2) } - - convertView.setTag(binding); + rowView.setTag(binding) } else { - binding = (ItemCountryBinding) convertView.getTag(); + binding = rowView.tag as ItemCountryBinding } - - var countryCode = cursor.getString(cursor.getColumnIndexOrThrow(Constants.COUNTRIES.COUNTRYSHORTCODE)); - binding.txtCountryShortCode.setText(countryCode); - var countryName = getCountryName(countryCode, cursor.getString(cursor.getColumnIndexOrThrow(Constants.COUNTRIES.COUNTRYNAME))); - binding.txtCountryName.setText(countryName); - - var newCountry = cursor.getString(1); - Log.i(TAG, newCountry); + val countryCode = + cursor!!.getString(cursor.getColumnIndexOrThrow(COUNTRIES.COUNTRYSHORTCODE)) + binding.txtCountryShortCode.text = countryCode + val countryName = getCountryName( + countryCode, + cursor.getString(cursor.getColumnIndexOrThrow(COUNTRIES.COUNTRYNAME)) + ) + binding.txtCountryName.text = countryName + val newCountry = cursor.getString(1) + Log.i(TAG, newCountry) if (selectedCountries.contains(newCountry)) { - binding.checkCountry.setChecked(false); - selectedCountries.remove(newCountry); + binding.checkCountry.isChecked = false + selectedCountries.remove(newCountry) } else { - binding.checkCountry.setChecked(true); - selectedCountries.add(newCountry); + binding.checkCountry.isChecked = true + selectedCountries.add(newCountry) } - } - private String getCountryName(final String countryCode, final String defaultName) { - int strId = context.getResources().getIdentifier("country_" + countryCode, "string", context.getPackageName()); - if (strId != 0) { - return context.getString(strId); - } - return defaultName; + private fun getCountryName(countryCode: String, defaultName: String): String { + val strId = + context.resources.getIdentifier("country_$countryCode", "string", context.packageName) + return if (strId != 0) { + context.getString(strId) + } else defaultName } - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - var binding = ItemCountryBinding.inflate(layoutInflater, parent, false); - var view = binding.getRoot(); - view.setTag(binding); - return view; + override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View { + val binding = ItemCountryBinding.inflate(layoutInflater, parent, false) + val view = binding.root + view.tag = binding + return view } - @Override - public void bindView(View view, Context context, Cursor cursor) { + override fun bindView(view: View, context: Context, cursor: Cursor) { //If you want to have zebra lines color effect uncomment below code - if (cursor.getPosition() % 2 == 1) { - view.setBackgroundResource(R.drawable.item_list_backgroundcolor); + if (cursor.position % 2 == 1) { + view.setBackgroundResource(R.drawable.item_list_backgroundcolor) } else { - view.setBackgroundResource(R.drawable.item_list_backgroundcolor2); + view.setBackgroundResource(R.drawable.item_list_backgroundcolor2) } - - var binding = (ItemCountryBinding) view.getTag(); - - var countryCode = cursor.getString(cursor.getColumnIndexOrThrow(Constants.COUNTRIES.COUNTRYSHORTCODE)); - binding.txtCountryShortCode.setText(countryCode); - var countryName = getCountryName(countryCode, cursor.getString(cursor.getColumnIndexOrThrow(Constants.COUNTRIES.COUNTRYNAME))); - binding.txtCountryName.setText(countryName); - - var newCountry = cursor.getString(1); - Log.i(TAG, newCountry); - binding.checkCountry.setChecked(selectedCountries.contains(newCountry)); - binding.checkCountry.setOnClickListener(onStateChangedListener(binding.checkCountry, cursor.getPosition())); + val binding = view.tag as ItemCountryBinding + val countryCode = cursor.getString(cursor.getColumnIndexOrThrow(COUNTRIES.COUNTRYSHORTCODE)) + binding.txtCountryShortCode.text = countryCode + val countryName = getCountryName( + countryCode, + cursor.getString(cursor.getColumnIndexOrThrow(COUNTRIES.COUNTRYNAME)) + ) + binding.txtCountryName.text = countryName + val newCountry = cursor.getString(1) + Log.i(TAG, newCountry) + binding.checkCountry.isChecked = selectedCountries.contains(newCountry) + binding.checkCountry.setOnClickListener( + onStateChangedListener( + binding.checkCountry, + cursor.position + ) + ) } - private View.OnClickListener onStateChangedListener(CheckBox checkCountry, int position) { - return v -> { - var cursor = (Cursor) getItem(position); - var country = cursor.getString(cursor.getColumnIndexOrThrow(Constants.COUNTRIES.COUNTRYSHORTCODE)); - if (checkCountry.isChecked()) { - selectedCountries.add(country); + private fun onStateChangedListener( + checkCountry: CheckBox, + position: Int + ): View.OnClickListener { + return View.OnClickListener { v: View? -> + val cursor = getItem(position) as Cursor + val country = cursor.getString(cursor.getColumnIndexOrThrow(COUNTRIES.COUNTRYSHORTCODE)) + if (checkCountry.isChecked) { + selectedCountries.add(country) } else { - selectedCountries.remove(country); + selectedCountries.remove(country) } - }; + } } - public Set getSelectedCountries() { - return selectedCountries; + fun getSelectedCountries(): Set { + return selectedCountries } - -} - +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/DbAdapter.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/DbAdapter.kt index abcb3acc..116ba33c 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/DbAdapter.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/DbAdapter.kt @@ -1,239 +1,192 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.db; - -import static java.util.stream.Collectors.joining; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteQueryBuilder; -import android.location.Location; -import android.util.Log; - -import androidx.annotation.NonNull; - -import org.apache.commons.lang3.StringUtils; - -import java.text.Normalizer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Predicate; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxStateQuery; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PhotoStation; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PhotoStations; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProblemType; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProviderApp; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Statistic; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Upload; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.UploadState; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.StationFilter; - -public class DbAdapter { - - private static final String TAG = DbAdapter.class.getSimpleName(); - - private static final String DATABASE_TABLE_STATIONS = "bahnhoefe"; - private static final String DATABASE_TABLE_COUNTRIES = "laender"; - private static final String DATABASE_TABLE_PROVIDER_APPS = "providerApps"; - private static final String DATABASE_TABLE_UPLOADS = "uploads"; - private static final String DATABASE_NAME = "bahnhoefe.db"; - private static final int DATABASE_VERSION = 22; - - private static final String CREATE_STATEMENT_STATIONS = "CREATE TABLE " + DATABASE_TABLE_STATIONS + " (" - + Constants.STATIONS.ROWID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + Constants.STATIONS.COUNTRY + " TEXT, " - + Constants.STATIONS.ID + " TEXT, " - + Constants.STATIONS.TITLE + " TEXT, " - + Constants.STATIONS.NORMALIZED_TITLE + " TEXT, " - + Constants.STATIONS.LAT + " REAL, " - + Constants.STATIONS.LON + " REAL, " - + Constants.STATIONS.PHOTO_ID + " INTEGER, " - + Constants.STATIONS.PHOTO_URL + " TEXT, " - + Constants.STATIONS.PHOTOGRAPHER + " TEXT, " - + Constants.STATIONS.PHOTOGRAPHER_URL + " TEXT, " - + Constants.STATIONS.LICENSE + " TEXT, " - + Constants.STATIONS.LICENSE_URL + " TEXT, " - + Constants.STATIONS.DS100 + " TEXT, " - + Constants.STATIONS.ACTIVE + " INTEGER, " - + Constants.STATIONS.OUTDATED + " INTEGER)"; - private static final String CREATE_STATEMENT_STATIONS_IDX = "CREATE INDEX " + DATABASE_TABLE_STATIONS + "_IDX " - + "ON " + DATABASE_TABLE_STATIONS + "(" + Constants.STATIONS.COUNTRY + ", " + Constants.STATIONS.ID + ")"; - private static final String CREATE_STATEMENT_COUNTRIES = "CREATE TABLE " + DATABASE_TABLE_COUNTRIES + " (" - + Constants.COUNTRIES.ROWID_COUNTRIES + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + Constants.COUNTRIES.COUNTRYSHORTCODE + " TEXT, " - + Constants.COUNTRIES.COUNTRYNAME + " TEXT, " - + Constants.COUNTRIES.EMAIL + " TEXT, " - + Constants.COUNTRIES.TIMETABLE_URL_TEMPLATE + " TEXT, " - + Constants.COUNTRIES.OVERRIDE_LICENSE + " TEXT)"; - private static final String CREATE_STATEMENT_PROVIDER_APPS = "CREATE TABLE " + DATABASE_TABLE_PROVIDER_APPS + " (" - + Constants.PROVIDER_APPS.COUNTRYSHORTCODE + " TEXT," - + Constants.PROVIDER_APPS.PA_TYPE + " TEXT," - + Constants.PROVIDER_APPS.PA_NAME + " TEXT, " - + Constants.PROVIDER_APPS.PA_URL + " TEXT)"; - private static final String CREATE_STATEMENT_UPLOADS = "CREATE TABLE " + DATABASE_TABLE_UPLOADS + " (" - + Constants.UPLOADS.ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + Constants.UPLOADS.STATION_ID + " TEXT, " - + Constants.UPLOADS.COUNTRY + " TEXT, " - + Constants.UPLOADS.REMOTE_ID + " INTEGER, " - + Constants.UPLOADS.TITLE + " TEXT, " - + Constants.UPLOADS.LAT + " REAL, " - + Constants.UPLOADS.LON + " REAL, " - + Constants.UPLOADS.COMMENT + " TEXT, " - + Constants.UPLOADS.INBOX_URL + " TEXT, " - + Constants.UPLOADS.PROBLEM_TYPE + " TEXT, " - + Constants.UPLOADS.REJECTED_REASON + " TEXT, " - + Constants.UPLOADS.UPLOAD_STATE + " TEXT, " - + Constants.UPLOADS.CREATED_AT + " INTEGER, " - + Constants.UPLOADS.ACTIVE + " INTEGER, " - + Constants.UPLOADS.CRC32 + " INTEGER)"; - - private static final String DROP_STATEMENT_STATIONS_IDX = "DROP INDEX IF EXISTS " + DATABASE_TABLE_STATIONS + "_IDX"; - private static final String DROP_STATEMENT_STATIONS = "DROP TABLE IF EXISTS " + DATABASE_TABLE_STATIONS; - private static final String DROP_STATEMENT_COUNTRIES = "DROP TABLE IF EXISTS " + DATABASE_TABLE_COUNTRIES; - private static final String DROP_STATEMENT_PROVIDER_APPS = "DROP TABLE IF EXISTS " + DATABASE_TABLE_PROVIDER_APPS; - - private final Context context; - private DbOpenHelper dbHelper; - private SQLiteDatabase db; - - public DbAdapter(Context context) { - this.context = context; - } - - public void open() { - dbHelper = new DbOpenHelper(context); - db = dbHelper.getWritableDatabase(); - } - - public void close() { - db.close(); - dbHelper.close(); - } - - public void insertStations(PhotoStations photoStations, String countryCode) { - db.beginTransaction(); +package de.bahnhoefe.deutschlands.bahnhofsfotos.db + +import android.content.ContentValues +import android.content.Context +import android.database.Cursor +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import android.database.sqlite.SQLiteQueryBuilder +import android.location.Location +import android.util.Log +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxStateQuery +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PhotoStation +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PhotoStations +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProblemType +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProviderApp +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Statistic +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Upload +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.UploadState +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants.COUNTRIES +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants.PROVIDER_APPS +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants.STATIONS +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants.UPLOADS +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.StationFilter +import org.apache.commons.lang3.StringUtils +import java.text.Normalizer +import java.util.Arrays +import java.util.function.Consumer +import java.util.function.Predicate +import java.util.stream.Collectors + +class DbAdapter(private val context: Context) { + private var dbHelper: DbOpenHelper? = null + private var db: SQLiteDatabase? = null + fun open() { + dbHelper = DbOpenHelper(context) + db = dbHelper!!.writableDatabase + } + + fun close() { + db!!.close() + dbHelper!!.close() + } + + fun insertStations(photoStations: PhotoStations, countryCode: String?) { + db!!.beginTransaction() try { - deleteStations(Set.of(countryCode)); - photoStations.getStations().forEach(station -> db.insert(DATABASE_TABLE_STATIONS, null, toContentValues(station, photoStations))); - db.setTransactionSuccessful(); + deleteStations(java.util.Set.of(countryCode)) + photoStations.stations.forEach(Consumer { station: PhotoStation -> + db!!.insert( + DATABASE_TABLE_STATIONS, null, toContentValues(station, photoStations) + ) + }) + db!!.setTransactionSuccessful() } finally { - db.endTransaction(); - } - } - - private ContentValues toContentValues(PhotoStation station, PhotoStations photoStations) { - var values = new ContentValues(); - values.put(Constants.STATIONS.ID, station.getId()); - values.put(Constants.STATIONS.COUNTRY, station.getCountry()); - values.put(Constants.STATIONS.TITLE, station.getTitle()); - values.put(Constants.STATIONS.NORMALIZED_TITLE, StringUtils.replaceChars(StringUtils.deleteWhitespace(StringUtils.stripAccents(Normalizer.normalize(station.getTitle(), Normalizer.Form.NFC))), "-_()", null)); - values.put(Constants.STATIONS.LAT, station.getLat()); - values.put(Constants.STATIONS.LON, station.getLon()); - values.put(Constants.STATIONS.DS100, station.getShortCode()); - values.put(Constants.STATIONS.ACTIVE, !station.getInactive()); - if (station.getPhotos().size() > 0) { - var photo = station.getPhotos().get(0); - values.put(Constants.STATIONS.PHOTO_ID, photo.getId()); - values.put(Constants.STATIONS.PHOTO_URL, photoStations.getPhotoBaseUrl() + photo.getPath()); - values.put(Constants.STATIONS.PHOTOGRAPHER, photo.getPhotographer()); - values.put(Constants.STATIONS.OUTDATED, photo.getOutdated()); - values.put(Constants.STATIONS.PHOTOGRAPHER_URL, photoStations.getPhotographerUrl(photo.getPhotographer())); - values.put(Constants.STATIONS.LICENSE, photoStations.getLicenseName(photo.getLicense())); - values.put(Constants.STATIONS.LICENSE_URL, photoStations.getLicenseUrl(photo.getLicense())); - } - return values; - } - - public void insertCountries(List countries) { + db!!.endTransaction() + } + } + + private fun toContentValues( + station: PhotoStation, + photoStations: PhotoStations + ): ContentValues { + val values = ContentValues() + values.put(STATIONS.ID, station.id) + values.put(STATIONS.COUNTRY, station.country) + values.put(STATIONS.TITLE, station.title) + values.put( + STATIONS.NORMALIZED_TITLE, StringUtils.replaceChars( + StringUtils.deleteWhitespace( + StringUtils.stripAccents( + Normalizer.normalize( + station.title, + Normalizer.Form.NFC + ) + ) + ), "-_()", null + ) + ) + values.put(STATIONS.LAT, station.lat) + values.put(STATIONS.LON, station.lon) + values.put(STATIONS.DS100, station.shortCode) + values.put(STATIONS.ACTIVE, !station.inactive) + if (station.photos!!.size > 0) { + val (id, photographer, path, _, license, outdated) = station.photos!![0] + values.put(STATIONS.PHOTO_ID, id) + values.put(STATIONS.PHOTO_URL, photoStations.photoBaseUrl + path) + values.put(STATIONS.PHOTOGRAPHER, photographer) + values.put(STATIONS.OUTDATED, outdated) + values.put(STATIONS.PHOTOGRAPHER_URL, photoStations.getPhotographerUrl(photographer)) + values.put(STATIONS.LICENSE, photoStations.getLicenseName(license)) + values.put(STATIONS.LICENSE_URL, photoStations.getLicenseUrl(license)) + } + return values + } + + fun insertCountries(countries: List) { if (countries.isEmpty()) { - return; + return } - db.beginTransaction(); + db!!.beginTransaction() try { - deleteCountries(); - countries.forEach(this::insertCountry); - db.setTransactionSuccessful(); + deleteCountries() + countries.forEach(Consumer { country: Country -> insertCountry(country) }) + db!!.setTransactionSuccessful() } finally { - db.endTransaction(); + db!!.endTransaction() } } - private void insertCountry(Country country) { - db.insert(DATABASE_TABLE_COUNTRIES, null, toContentValues(country)); - - country.getProviderApps().stream() - .map(p -> toContentValues(country.getCode(), p)) - .forEach(values -> db.insert(DATABASE_TABLE_PROVIDER_APPS, null, values)); + private fun insertCountry(country: Country) { + db!!.insert(DATABASE_TABLE_COUNTRIES, null, toContentValues(country)) + country.providerApps.stream() + .map { p: ProviderApp -> toContentValues(country.code, p) } + .forEach { values: ContentValues? -> + db!!.insert( + DATABASE_TABLE_PROVIDER_APPS, + null, + values + ) + } } - private ContentValues toContentValues(String countryCode, ProviderApp app) { - var values = new ContentValues(); - values.put(Constants.PROVIDER_APPS.COUNTRYSHORTCODE, countryCode); - values.put(Constants.PROVIDER_APPS.PA_TYPE, app.getType()); - values.put(Constants.PROVIDER_APPS.PA_NAME, app.getName()); - values.put(Constants.PROVIDER_APPS.PA_URL, app.getUrl()); - return values; + private fun toContentValues(countryCode: String, app: ProviderApp): ContentValues { + val values = ContentValues() + values.put(PROVIDER_APPS.COUNTRYSHORTCODE, countryCode) + values.put(PROVIDER_APPS.PA_TYPE, app.type) + values.put(PROVIDER_APPS.PA_NAME, app.name) + values.put(PROVIDER_APPS.PA_URL, app.url) + return values } - private ContentValues toContentValues(Country country) { - var values = new ContentValues(); - values.put(Constants.COUNTRIES.COUNTRYSHORTCODE, country.getCode()); - values.put(Constants.COUNTRIES.COUNTRYNAME, country.getName()); - values.put(Constants.COUNTRIES.EMAIL, country.getEmail()); - values.put(Constants.COUNTRIES.TIMETABLE_URL_TEMPLATE, country.getTimetableUrlTemplate()); - values.put(Constants.COUNTRIES.OVERRIDE_LICENSE, country.getOverrideLicense()); - return values; + private fun toContentValues(country: Country): ContentValues { + val values = ContentValues() + values.put(COUNTRIES.COUNTRYSHORTCODE, country.code) + values.put(COUNTRIES.COUNTRYNAME, country.name) + values.put(COUNTRIES.EMAIL, country.email) + values.put(COUNTRIES.TIMETABLE_URL_TEMPLATE, country.timetableUrlTemplate) + values.put(COUNTRIES.OVERRIDE_LICENSE, country.overrideLicense) + return values } - public Upload insertUpload(Upload upload) { - upload.setId(db.insert(DATABASE_TABLE_UPLOADS, null, toContentValues(upload))); - return upload; + fun insertUpload(upload: Upload): Upload { + upload.id = db!!.insert(DATABASE_TABLE_UPLOADS, null, toContentValues(upload)) + return upload } - public void deleteStations(Set countryCodes) { - db.delete(DATABASE_TABLE_STATIONS, whereCountryCodeIn(countryCodes), null); + fun deleteStations(countryCodes: Set?) { + db!!.delete(DATABASE_TABLE_STATIONS, whereCountryCodeIn(countryCodes), null) } - public void deleteCountries() { - db.delete(DATABASE_TABLE_PROVIDER_APPS, null, null); - db.delete(DATABASE_TABLE_COUNTRIES, null, null); + fun deleteCountries() { + db!!.delete(DATABASE_TABLE_PROVIDER_APPS, null, null) + db!!.delete(DATABASE_TABLE_COUNTRIES, null, null) } - private String getStationOrderBy(boolean sortByDistance, Location myPos) { - var orderBy = Constants.STATIONS.TITLE + " ASC"; - + private fun getStationOrderBy(sortByDistance: Boolean, myPos: Location?): String { + var orderBy = STATIONS.TITLE + " ASC" if (sortByDistance) { - var fudge = Math.pow(Math.cos(Math.toRadians(myPos.getLatitude())), 2); - orderBy = "((" + myPos.getLatitude() + " - " + Constants.STATIONS.LAT + ") * (" + myPos.getLatitude() + " - " + Constants.STATIONS.LAT + ") + " + - "(" + myPos.getLongitude() + " - " + Constants.STATIONS.LON + ") * (" + myPos.getLongitude() + " - " + Constants.STATIONS.LON + ") * " + fudge + ")"; - } - - return orderBy; - } - - public Cursor getCountryList() { - var selectCountries = "SELECT " + Constants.COUNTRIES.ROWID_COUNTRIES + " AS " + Constants.CURSOR_ADAPTER_ID + ", " + - Constants.COUNTRIES.COUNTRYSHORTCODE + ", " + Constants.COUNTRIES.COUNTRYNAME + - " FROM " + DATABASE_TABLE_COUNTRIES + " ORDER BY " + Constants.COUNTRIES.COUNTRYNAME + " ASC"; - Log.d(TAG, selectCountries); - - var cursor = db.rawQuery(selectCountries, null); - - if (!cursor.moveToFirst()) { - cursor.close(); - return null; + val fudge = Math.pow( + Math.cos( + Math.toRadians( + myPos!!.latitude + ) + ), 2.0 + ) + orderBy = + "((" + myPos.latitude + " - " + STATIONS.LAT + ") * (" + myPos.latitude + " - " + STATIONS.LAT + ") + " + + "(" + myPos.longitude + " - " + STATIONS.LON + ") * (" + myPos.longitude + " - " + STATIONS.LON + ") * " + fudge + ")" + } + return orderBy + } + + val countryList: Cursor? + get() { + val selectCountries = + "SELECT " + COUNTRIES.ROWID_COUNTRIES + " AS " + Constants.CURSOR_ADAPTER_ID + ", " + + COUNTRIES.COUNTRYSHORTCODE + ", " + COUNTRIES.COUNTRYNAME + + " FROM " + DATABASE_TABLE_COUNTRIES + " ORDER BY " + COUNTRIES.COUNTRYNAME + " ASC" + Log.d(TAG, selectCountries) + val cursor = db!!.rawQuery(selectCountries, null) + if (!cursor.moveToFirst()) { + cursor.close() + return null + } + return cursor } - return cursor; - } - /** * Return a cursor on station ids where the station's title matches the given string @@ -245,536 +198,669 @@ public class DbAdapter { * @param myPos current location * @return a Cursor representing the matching results */ - public Cursor getStationsListByKeyword(String search, StationFilter stationFilter, Set countryCodes, boolean sortByDistance, Location myPos) { - var selectQuery = whereCountryCodeIn(countryCodes); - var queryArgs = new ArrayList(); - + fun getStationsListByKeyword( + search: String?, + stationFilter: StationFilter?, + countryCodes: Set?, + sortByDistance: Boolean, + myPos: Location? + ): Cursor? { + var selectQuery = whereCountryCodeIn(countryCodes) + val queryArgs = ArrayList() if (StringUtils.isNotBlank(search)) { - selectQuery += String.format(" AND %s LIKE ?", Constants.STATIONS.NORMALIZED_TITLE); - queryArgs.add("%" + StringUtils.replaceChars(StringUtils.stripAccents(StringUtils.trimToEmpty(search)), " -_()", "%%%%%") + "%"); + selectQuery += String.format(" AND %s LIKE ?", STATIONS.NORMALIZED_TITLE) + queryArgs.add( + "%" + StringUtils.replaceChars( + StringUtils.stripAccents( + StringUtils.trimToEmpty( + search + ) + ), " -_()", "%%%%%" + ) + "%" + ) } - if (stationFilter.getNickname() != null) { - selectQuery += " AND " + Constants.STATIONS.PHOTOGRAPHER + " = ?"; - queryArgs.add(stationFilter.getNickname()); - } - if (stationFilter.hasPhoto() != null) { - selectQuery += " AND " + Constants.STATIONS.PHOTO_URL + " IS " + (stationFilter.hasPhoto() ? "NOT" : "") + " NULL"; - } - if (stationFilter.isActive() != null) { - selectQuery += " AND " + Constants.STATIONS.ACTIVE + " = ?"; - queryArgs.add(stationFilter.isActive() ? "1" : "0"); - } - - Log.w(TAG, selectQuery); - - var cursor = db.query(DATABASE_TABLE_STATIONS, - new String[]{ - Constants.STATIONS.ROWID + " AS " + Constants.CURSOR_ADAPTER_ID, - Constants.STATIONS.ID, - Constants.STATIONS.TITLE, - Constants.STATIONS.PHOTO_URL, - Constants.STATIONS.COUNTRY - }, - selectQuery, - queryArgs.toArray(new String[0]), null, null, getStationOrderBy(sortByDistance, myPos)); - + selectQuery += " AND " + STATIONS.PHOTOGRAPHER + " = ?" + queryArgs.add(stationFilter.getNickname()) + } + if (stationFilter!!.hasPhoto() != null) { + selectQuery += " AND " + STATIONS.PHOTO_URL + " IS " + (if (stationFilter.hasPhoto()!!) "NOT" else "") + " NULL" + } + if (stationFilter.isActive != null) { + selectQuery += " AND " + STATIONS.ACTIVE + " = ?" + queryArgs.add(if (stationFilter.isActive) "1" else "0") + } + Log.w(TAG, selectQuery) + val cursor = db!!.query( + DATABASE_TABLE_STATIONS, arrayOf( + STATIONS.ROWID + " AS " + Constants.CURSOR_ADAPTER_ID, + STATIONS.ID, + STATIONS.TITLE, + STATIONS.PHOTO_URL, + STATIONS.COUNTRY + ), + selectQuery, + queryArgs.toTypedArray(), null, null, getStationOrderBy(sortByDistance, myPos) + ) if (!cursor.moveToFirst()) { - Log.w(TAG, String.format("Query '%s' returned no result", search)); - cursor.close(); - return null; + Log.w(TAG, String.format("Query '%s' returned no result", search)) + cursor.close() + return null } - return cursor; + return cursor } - private String whereCountryCodeIn(Set countryCodes) { - return Constants.STATIONS.COUNTRY + + private fun whereCountryCodeIn(countryCodes: Set?): String { + return STATIONS.COUNTRY + " IN (" + - countryCodes.stream().map(c -> "'" + c + "'").collect(joining(",")) + - ")"; + countryCodes!!.stream().map { c: String? -> "'$c'" } + .collect(Collectors.joining(",")) + + ")" } - public Statistic getStatistic(String country) { - try (var cursor = db.rawQuery("SELECT COUNT(*), COUNT(" + Constants.STATIONS.PHOTO_URL + "), COUNT(DISTINCT(" + Constants.STATIONS.PHOTOGRAPHER + ")) FROM " + DATABASE_TABLE_STATIONS + " WHERE " + Constants.STATIONS.COUNTRY + " = ?", new String[]{country})) { + fun getStatistic(country: String?): Statistic? { + db!!.rawQuery( + "SELECT COUNT(*), COUNT(" + STATIONS.PHOTO_URL + "), COUNT(DISTINCT(" + STATIONS.PHOTOGRAPHER + ")) FROM " + DATABASE_TABLE_STATIONS + " WHERE " + STATIONS.COUNTRY + " = ?", + arrayOf(country) + ).use { cursor -> if (cursor.moveToNext()) { - return new Statistic(cursor.getInt(0), - cursor.getInt(1), - cursor.getInt(0) - cursor.getInt(1), - cursor.getInt(2)); + return Statistic( + cursor.getInt(0), + cursor.getInt(1), + cursor.getInt(0) - cursor.getInt(1), + cursor.getInt(2) + ) } } - return null; + return null } - public String[] getPhotographerNicknames() { - var photographers = new ArrayList(); - try (var cursor = db.rawQuery("SELECT distinct " + Constants.STATIONS.PHOTOGRAPHER + " FROM " + DATABASE_TABLE_STATIONS + " WHERE " + Constants.STATIONS.PHOTOGRAPHER + " IS NOT NULL ORDER BY " + Constants.STATIONS.PHOTOGRAPHER, null)) { - while (cursor.moveToNext()) { - photographers.add(cursor.getString(0)); + val photographerNicknames: Array + get() { + val photographers = ArrayList() + db!!.rawQuery( + "SELECT distinct " + STATIONS.PHOTOGRAPHER + " FROM " + DATABASE_TABLE_STATIONS + " WHERE " + STATIONS.PHOTOGRAPHER + " IS NOT NULL ORDER BY " + STATIONS.PHOTOGRAPHER, + null + ).use { cursor -> + while (cursor.moveToNext()) { + photographers.add(cursor.getString(0)) + } } + return photographers.toTypedArray() } - return photographers.toArray(new String[0]); - } - public int countStations(Set countryCodes) { - try (var query = db.rawQuery("SELECT COUNT(*) FROM " + DATABASE_TABLE_STATIONS + " WHERE " + whereCountryCodeIn(countryCodes), null)) { + fun countStations(countryCodes: Set?): Int { + db!!.rawQuery( + "SELECT COUNT(*) FROM " + DATABASE_TABLE_STATIONS + " WHERE " + whereCountryCodeIn( + countryCodes + ), null + ).use { query -> if (query.moveToFirst()) { - return query.getInt(0); + return query.getInt(0) } } - return 0; + return 0 } - public void updateUpload(Upload upload) { - db.beginTransaction(); + fun updateUpload(upload: Upload?) { + db!!.beginTransaction() try { - db.update(DATABASE_TABLE_UPLOADS, toContentValues(upload), Constants.UPLOADS.ID + " = ?", new String[]{String.valueOf(upload.getId())}); - db.setTransactionSuccessful(); + db!!.update( + DATABASE_TABLE_UPLOADS, toContentValues(upload), UPLOADS.ID + " = ?", arrayOf( + upload!!.id.toString() + ) + ) + db!!.setTransactionSuccessful() } finally { - db.endTransaction(); - } - } - - private ContentValues toContentValues(Upload upload) { - var values = new ContentValues(); - values.put(Constants.UPLOADS.COMMENT, upload.getComment()); - values.put(Constants.UPLOADS.COUNTRY, upload.getCountry()); - values.put(Constants.UPLOADS.CREATED_AT, upload.getCreatedAt()); - values.put(Constants.UPLOADS.INBOX_URL, upload.getInboxUrl()); - values.put(Constants.UPLOADS.LAT, upload.getLat()); - values.put(Constants.UPLOADS.LON, upload.getLon()); - values.put(Constants.UPLOADS.PROBLEM_TYPE, upload.getProblemType() != null ? upload.getProblemType().name() : null); - values.put(Constants.UPLOADS.REJECTED_REASON, upload.getRejectReason()); - values.put(Constants.UPLOADS.REMOTE_ID, upload.getRemoteId()); - values.put(Constants.UPLOADS.STATION_ID, upload.getStationId()); - values.put(Constants.UPLOADS.TITLE, upload.getTitle()); - values.put(Constants.UPLOADS.UPLOAD_STATE, upload.getUploadState().name()); - values.put(Constants.UPLOADS.ACTIVE, upload.getActive()); - values.put(Constants.UPLOADS.CRC32, upload.getCrc32()); - values.put(Constants.UPLOADS.REMOTE_ID, upload.getRemoteId()); - return values; - } - - public List getPendingUploadsForStation(Station station) { - var uploads = new ArrayList(); - try (var cursor = db.query(DATABASE_TABLE_UPLOADS, null, Constants.UPLOADS.COUNTRY + " = ? AND " + Constants.UPLOADS.STATION_ID + " = ? AND " + getPendingUploadWhereClause(), - new String[]{station.getCountry(), station.getId()}, null, null, Constants.UPLOADS.CREATED_AT + " DESC")) { + db!!.endTransaction() + } + } + + private fun toContentValues(upload: Upload?): ContentValues { + val values = ContentValues() + values.put(UPLOADS.COMMENT, upload!!.comment) + values.put(UPLOADS.COUNTRY, upload.country) + values.put(UPLOADS.CREATED_AT, upload.createdAt) + values.put(UPLOADS.INBOX_URL, upload.inboxUrl) + values.put(UPLOADS.LAT, upload.lat) + values.put(UPLOADS.LON, upload.lon) + values.put( + UPLOADS.PROBLEM_TYPE, + if (upload.problemType != null) upload.problemType!!.name else null + ) + values.put(UPLOADS.REJECTED_REASON, upload.rejectReason) + values.put(UPLOADS.REMOTE_ID, upload.remoteId) + values.put(UPLOADS.STATION_ID, upload.stationId) + values.put(UPLOADS.TITLE, upload.title) + values.put(UPLOADS.UPLOAD_STATE, upload.uploadState.name) + values.put(UPLOADS.ACTIVE, upload.active) + values.put(UPLOADS.CRC32, upload.crc32) + values.put(UPLOADS.REMOTE_ID, upload.remoteId) + return values + } + + fun getPendingUploadsForStation(station: Station): List { + val uploads = ArrayList() + db!!.query( + DATABASE_TABLE_UPLOADS, + null, + UPLOADS.COUNTRY + " = ? AND " + UPLOADS.STATION_ID + " = ? AND " + pendingUploadWhereClause, + arrayOf(station.country, station.id), + null, + null, + UPLOADS.CREATED_AT + " DESC" + ).use { cursor -> while (cursor.moveToNext()) { - uploads.add(createUploadFromCursor(cursor)); + uploads.add(createUploadFromCursor(cursor)) } } - - return uploads; + return uploads } - public Upload getPendingUploadForCoordinates(double lat, double lon) { - try (var cursor = db.query(DATABASE_TABLE_UPLOADS, null, Constants.UPLOADS.LAT + " = ? AND " + Constants.UPLOADS.LON + " = ? AND " + getPendingUploadWhereClause(), - new String[]{String.valueOf(lat), String.valueOf(lon)}, null, null, Constants.UPLOADS.CREATED_AT + " DESC")) { + fun getPendingUploadForCoordinates(lat: Double, lon: Double): Upload? { + db!!.query( + DATABASE_TABLE_UPLOADS, + null, + UPLOADS.LAT + " = ? AND " + UPLOADS.LON + " = ? AND " + pendingUploadWhereClause, + arrayOf(lat.toString(), lon.toString()), + null, + null, + UPLOADS.CREATED_AT + " DESC" + ).use { cursor -> if (cursor.moveToFirst()) { - return createUploadFromCursor(cursor); + return createUploadFromCursor(cursor) } } - - return null; + return null } - private String getUploadWhereClause(Predicate predicate) { - return Constants.UPLOADS.UPLOAD_STATE + " IN (" + + private fun getUploadWhereClause(predicate: Predicate): String { + return UPLOADS.UPLOAD_STATE + " IN (" + Arrays.stream(UploadState.values()) - .filter(predicate) - .map(s -> "'" + s.name() + "'") - .collect(joining(",")) + - ')'; - } - - private String getPendingUploadWhereClause() { - return getUploadWhereClause(UploadState::isPending); - } - - private String getCompletedUploadWhereClause() { - return getUploadWhereClause(s -> !s.isPending()); - } - - public Cursor getOutbox() { - var queryBuilder = new SQLiteQueryBuilder(); - queryBuilder.setTables(DATABASE_TABLE_UPLOADS - + " LEFT JOIN " - + DATABASE_TABLE_STATIONS - + " ON " - + DATABASE_TABLE_STATIONS + "." + Constants.STATIONS.COUNTRY - + " = " - + DATABASE_TABLE_UPLOADS + "." + Constants.UPLOADS.COUNTRY - + " AND " - + DATABASE_TABLE_STATIONS + "." + Constants.STATIONS.ID - + " = " - + DATABASE_TABLE_UPLOADS + "." + Constants.UPLOADS.STATION_ID); - return queryBuilder.query(db, new String[]{ - DATABASE_TABLE_UPLOADS + "." + Constants.UPLOADS.ID + " AS " + Constants.CURSOR_ADAPTER_ID, - DATABASE_TABLE_UPLOADS + "." + Constants.UPLOADS.REMOTE_ID, - DATABASE_TABLE_UPLOADS + "." + Constants.UPLOADS.COUNTRY, - DATABASE_TABLE_UPLOADS + "." + Constants.UPLOADS.STATION_ID, - DATABASE_TABLE_UPLOADS + "." + Constants.UPLOADS.TITLE, - DATABASE_TABLE_UPLOADS + "." + Constants.UPLOADS.UPLOAD_STATE, - DATABASE_TABLE_UPLOADS + "." + Constants.UPLOADS.PROBLEM_TYPE, - DATABASE_TABLE_UPLOADS + "." + Constants.UPLOADS.COMMENT, - DATABASE_TABLE_UPLOADS + "." + Constants.UPLOADS.REJECTED_REASON, - DATABASE_TABLE_STATIONS + "." + Constants.UPLOADS.TITLE + " AS " + Constants.UPLOADS.JOIN_STATION_TITLE - }, null, null, null, null, Constants.UPLOADS.CREATED_AT + " DESC"); - } - - public Upload getUploadById(long id) { - try (var cursor = db.query(DATABASE_TABLE_UPLOADS, null, Constants.UPLOADS.ID + "=?", - new String[]{String.valueOf(id)}, null, null, null)) { + .filter(predicate) + .map { s: UploadState -> "'" + s.name + "'" } + .collect(Collectors.joining(",")) + + ')' + } + + private val pendingUploadWhereClause: String + private get() = getUploadWhereClause(UploadState::isPending) + private val completedUploadWhereClause: String + private get() = getUploadWhereClause { s: UploadState -> !s.isPending } + val outbox: Cursor + get() { + val queryBuilder = SQLiteQueryBuilder() + queryBuilder.tables = (DATABASE_TABLE_UPLOADS + + " LEFT JOIN " + + DATABASE_TABLE_STATIONS + + " ON " + + DATABASE_TABLE_STATIONS + "." + STATIONS.COUNTRY + + " = " + + DATABASE_TABLE_UPLOADS + "." + UPLOADS.COUNTRY + + " AND " + + DATABASE_TABLE_STATIONS + "." + STATIONS.ID + + " = " + + DATABASE_TABLE_UPLOADS + "." + UPLOADS.STATION_ID) + return queryBuilder.query( + db, arrayOf( + DATABASE_TABLE_UPLOADS + "." + UPLOADS.ID + " AS " + Constants.CURSOR_ADAPTER_ID, + DATABASE_TABLE_UPLOADS + "." + UPLOADS.REMOTE_ID, + DATABASE_TABLE_UPLOADS + "." + UPLOADS.COUNTRY, + DATABASE_TABLE_UPLOADS + "." + UPLOADS.STATION_ID, + DATABASE_TABLE_UPLOADS + "." + UPLOADS.TITLE, + DATABASE_TABLE_UPLOADS + "." + UPLOADS.UPLOAD_STATE, + DATABASE_TABLE_UPLOADS + "." + UPLOADS.PROBLEM_TYPE, + DATABASE_TABLE_UPLOADS + "." + UPLOADS.COMMENT, + DATABASE_TABLE_UPLOADS + "." + UPLOADS.REJECTED_REASON, + DATABASE_TABLE_STATIONS + "." + UPLOADS.TITLE + " AS " + UPLOADS.JOIN_STATION_TITLE + ), null, null, null, null, UPLOADS.CREATED_AT + " DESC" + ) + } + + fun getUploadById(id: Long): Upload? { + db!!.query( + DATABASE_TABLE_UPLOADS, + null, + UPLOADS.ID + "=?", + arrayOf(id.toString()), + null, + null, + null + ).use { cursor -> if (cursor.moveToFirst()) { - return createUploadFromCursor(cursor); + return createUploadFromCursor(cursor) } } - - return null; + return null } - public void deleteUpload(long id) { - db.delete(DATABASE_TABLE_UPLOADS, Constants.UPLOADS.ID + "=?", new String[]{String.valueOf(id)}); + fun deleteUpload(id: Long) { + db!!.delete(DATABASE_TABLE_UPLOADS, UPLOADS.ID + "=?", arrayOf(id.toString())) } - public List getPendingUploads(boolean withRemoteId) { - var selection = getPendingUploadWhereClause(); + fun getPendingUploads(withRemoteId: Boolean): List { + var selection = pendingUploadWhereClause if (withRemoteId) { - selection += " AND " + Constants.UPLOADS.REMOTE_ID + " IS NOT NULL"; + selection += " AND " + UPLOADS.REMOTE_ID + " IS NOT NULL" } - var uploads = new ArrayList(); - try (var cursor = db.query(DATABASE_TABLE_UPLOADS, null, selection, - null, null, null, null)) { + val uploads = ArrayList() + db!!.query( + DATABASE_TABLE_UPLOADS, null, selection, + null, null, null, null + ).use { cursor -> while (cursor.moveToNext()) { - uploads.add(createUploadFromCursor(cursor)); + uploads.add(createUploadFromCursor(cursor)) } } - - return uploads; + return uploads } - public void updateUploadStates(List stateQueries) { - db.beginTransaction(); + fun updateUploadStates(stateQueries: List) { + db!!.beginTransaction() try { - stateQueries.forEach(state -> db.update(DATABASE_TABLE_UPLOADS, toUploadStatesContentValues(state), Constants.UPLOADS.REMOTE_ID + " = ?", new String[]{String.valueOf(state.getId())})); - db.setTransactionSuccessful(); + stateQueries.forEach(Consumer { state: InboxStateQuery -> + db!!.update( + DATABASE_TABLE_UPLOADS, + toUploadStatesContentValues(state), + UPLOADS.REMOTE_ID + " = ?", + arrayOf(state.id.toString()) + ) + }) + db!!.setTransactionSuccessful() } finally { - db.endTransaction(); + db!!.endTransaction() } } - private ContentValues toUploadStatesContentValues(InboxStateQuery state) { - var values = new ContentValues(); - values.put(Constants.UPLOADS.UPLOAD_STATE, state.getState().name()); - values.put(Constants.UPLOADS.REJECTED_REASON, state.getRejectedReason()); - values.put(Constants.UPLOADS.CRC32, state.getCrc32()); - return values; + private fun toUploadStatesContentValues(state: InboxStateQuery): ContentValues { + val values = ContentValues() + values.put(UPLOADS.UPLOAD_STATE, state.state.name) + values.put(UPLOADS.REJECTED_REASON, state.rejectedReason) + values.put(UPLOADS.CRC32, state.crc32) + return values } - public List getCompletedUploads() { - var uploads = new ArrayList(); - try (var cursor = db.query(DATABASE_TABLE_UPLOADS, null, - Constants.UPLOADS.REMOTE_ID + " IS NOT NULL AND " + getCompletedUploadWhereClause(), - null, null, null, null)) { - while (cursor.moveToNext()) { - uploads.add(createUploadFromCursor(cursor)); + val completedUploads: List + get() { + val uploads = ArrayList() + db!!.query( + DATABASE_TABLE_UPLOADS, null, + UPLOADS.REMOTE_ID + " IS NOT NULL AND " + completedUploadWhereClause, + null, null, null, null + ).use { cursor -> + while (cursor.moveToNext()) { + uploads.add(createUploadFromCursor(cursor)) + } } + return uploads } - return uploads; - } - - static class DbOpenHelper extends SQLiteOpenHelper { - - DbOpenHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); + internal class DbOpenHelper(context: Context?) : + SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { + override fun onCreate(db: SQLiteDatabase) { + Log.i(TAG, "Creating database") + db.execSQL(CREATE_STATEMENT_STATIONS) + db.execSQL(CREATE_STATEMENT_STATIONS_IDX) + db.execSQL(CREATE_STATEMENT_COUNTRIES) + db.execSQL(CREATE_STATEMENT_PROVIDER_APPS) + db.execSQL(CREATE_STATEMENT_UPLOADS) + Log.i(TAG, "Database structure created.") } - @Override - public void onCreate(SQLiteDatabase db) { - Log.i(TAG, "Creating database"); - db.execSQL(CREATE_STATEMENT_STATIONS); - db.execSQL(CREATE_STATEMENT_STATIONS_IDX); - db.execSQL(CREATE_STATEMENT_COUNTRIES); - db.execSQL(CREATE_STATEMENT_PROVIDER_APPS); - db.execSQL(CREATE_STATEMENT_UPLOADS); - Log.i(TAG, "Database structure created."); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - Log.w(TAG, "Upgrade database from version" + oldVersion + " to " + newVersion); - - db.beginTransaction(); - + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + Log.w(TAG, "Upgrade database from version$oldVersion to $newVersion") + db.beginTransaction() if (oldVersion < 13) { // up to version 13 we dropped all tables and recreated them - db.execSQL(DROP_STATEMENT_STATIONS_IDX); - db.execSQL(DROP_STATEMENT_STATIONS); - db.execSQL(DROP_STATEMENT_COUNTRIES); - db.execSQL(DROP_STATEMENT_PROVIDER_APPS); - onCreate(db); + db.execSQL(DROP_STATEMENT_STATIONS_IDX) + db.execSQL(DROP_STATEMENT_STATIONS) + db.execSQL(DROP_STATEMENT_COUNTRIES) + db.execSQL(DROP_STATEMENT_PROVIDER_APPS) + onCreate(db) } else { // from now on we need to preserve user data and perform schema changes selectively - if (oldVersion < 14) { - db.execSQL(CREATE_STATEMENT_UPLOADS); + db.execSQL(CREATE_STATEMENT_UPLOADS) } - if (oldVersion < 15) { - db.execSQL(DROP_STATEMENT_STATIONS_IDX); - db.execSQL(CREATE_STATEMENT_STATIONS_IDX); + db.execSQL(DROP_STATEMENT_STATIONS_IDX) + db.execSQL(CREATE_STATEMENT_STATIONS_IDX) } - if (oldVersion < 16) { - db.execSQL("ALTER TABLE " + DATABASE_TABLE_COUNTRIES + " ADD COLUMN " + Constants.COUNTRIES.OVERRIDE_LICENSE + " TEXT"); + db.execSQL("ALTER TABLE " + DATABASE_TABLE_COUNTRIES + " ADD COLUMN " + COUNTRIES.OVERRIDE_LICENSE + " TEXT") } - if (oldVersion < 17) { - db.execSQL("ALTER TABLE " + DATABASE_TABLE_UPLOADS + " ADD COLUMN " + Constants.UPLOADS.ACTIVE + " INTEGER"); + db.execSQL("ALTER TABLE " + DATABASE_TABLE_UPLOADS + " ADD COLUMN " + UPLOADS.ACTIVE + " INTEGER") } - if (oldVersion < 18) { - db.execSQL("ALTER TABLE " + DATABASE_TABLE_STATIONS + " ADD COLUMN " + Constants.STATIONS.NORMALIZED_TITLE + " TEXT"); - db.execSQL("UPDATE " + DATABASE_TABLE_STATIONS + " SET " + Constants.STATIONS.NORMALIZED_TITLE + " = " + Constants.STATIONS.TITLE); + db.execSQL("ALTER TABLE " + DATABASE_TABLE_STATIONS + " ADD COLUMN " + STATIONS.NORMALIZED_TITLE + " TEXT") + db.execSQL("UPDATE " + DATABASE_TABLE_STATIONS + " SET " + STATIONS.NORMALIZED_TITLE + " = " + STATIONS.TITLE) } - if (oldVersion < 19) { - db.execSQL("ALTER TABLE " + DATABASE_TABLE_UPLOADS + " ADD COLUMN " + Constants.UPLOADS.CRC32 + " INTEGER"); + db.execSQL("ALTER TABLE " + DATABASE_TABLE_UPLOADS + " ADD COLUMN " + UPLOADS.CRC32 + " INTEGER") } - if (oldVersion < 20) { - db.execSQL("ALTER TABLE " + DATABASE_TABLE_STATIONS + " ADD COLUMN " + Constants.STATIONS.OUTDATED + " INTEGER"); + db.execSQL("ALTER TABLE " + DATABASE_TABLE_STATIONS + " ADD COLUMN " + STATIONS.OUTDATED + " INTEGER") } - if (oldVersion < 21) { - db.execSQL("ALTER TABLE " + DATABASE_TABLE_STATIONS + " ADD COLUMN " + Constants.STATIONS.PHOTO_ID + " INTEGER"); + db.execSQL("ALTER TABLE " + DATABASE_TABLE_STATIONS + " ADD COLUMN " + STATIONS.PHOTO_ID + " INTEGER") } - } - - db.setTransactionSuccessful(); - db.endTransaction(); - } - } - - @NonNull - private Station createStationFromCursor(@NonNull Cursor cursor) { - return new Station( - cursor.getString(cursor.getColumnIndexOrThrow(Constants.STATIONS.COUNTRY)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.STATIONS.ID)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.STATIONS.TITLE)), - cursor.getDouble(cursor.getColumnIndexOrThrow(Constants.STATIONS.LAT)), - cursor.getDouble(cursor.getColumnIndexOrThrow(Constants.STATIONS.LON)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.STATIONS.DS100)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.STATIONS.PHOTO_URL)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.STATIONS.PHOTOGRAPHER)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.STATIONS.PHOTOGRAPHER_URL)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.STATIONS.LICENSE)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.STATIONS.LICENSE_URL)), - Boolean.TRUE.equals(getBoolean(cursor, Constants.STATIONS.ACTIVE)), - Boolean.TRUE.equals(getBoolean(cursor, Constants.STATIONS.OUTDATED)), - cursor.getLong(cursor.getColumnIndexOrThrow(Constants.STATIONS.PHOTO_ID)) - ); - } - - @NonNull - private Country createCountryFromCursor(@NonNull Cursor cursor) { - return new Country( - cursor.getString(cursor.getColumnIndexOrThrow(Constants.COUNTRIES.COUNTRYSHORTCODE)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.COUNTRIES.COUNTRYNAME)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.COUNTRIES.EMAIL)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.COUNTRIES.TIMETABLE_URL_TEMPLATE)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.COUNTRIES.OVERRIDE_LICENSE))); - } - - @NonNull - private ProviderApp createProviderAppFromCursor(@NonNull Cursor cursor) { - return new ProviderApp( - cursor.getString(cursor.getColumnIndexOrThrow(Constants.PROVIDER_APPS.PA_TYPE)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.PROVIDER_APPS.PA_NAME)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.PROVIDER_APPS.PA_URL))); - } - - @NonNull - private Upload createUploadFromCursor(@NonNull Cursor cursor) { - var upload = new Upload( - cursor.getLong(cursor.getColumnIndexOrThrow(Constants.UPLOADS.ID)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.COUNTRY)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.STATION_ID)), - getLong(cursor, Constants.UPLOADS.REMOTE_ID), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.TITLE)), - getDouble(cursor, Constants.UPLOADS.LAT), - getDouble(cursor, Constants.UPLOADS.LON), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.COMMENT)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.INBOX_URL)), - null, - cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.REJECTED_REASON)), - UploadState.UNKNOWN, - cursor.getLong(cursor.getColumnIndexOrThrow(Constants.UPLOADS.CREATED_AT)), - getBoolean(cursor, Constants.UPLOADS.ACTIVE), - getLong(cursor, Constants.UPLOADS.CRC32)); - - var problemType = cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.PROBLEM_TYPE)); + db.setTransactionSuccessful() + db.endTransaction() + } + } + + private fun createStationFromCursor(cursor: Cursor): Station { + return Station( + cursor.getString(cursor.getColumnIndexOrThrow(STATIONS.COUNTRY)), + cursor.getString(cursor.getColumnIndexOrThrow(STATIONS.ID)), + cursor.getString(cursor.getColumnIndexOrThrow(STATIONS.TITLE)), + cursor.getDouble(cursor.getColumnIndexOrThrow(STATIONS.LAT)), + cursor.getDouble(cursor.getColumnIndexOrThrow(STATIONS.LON)), + cursor.getString(cursor.getColumnIndexOrThrow(STATIONS.DS100)), + cursor.getString(cursor.getColumnIndexOrThrow(STATIONS.PHOTO_URL)), + cursor.getString(cursor.getColumnIndexOrThrow(STATIONS.PHOTOGRAPHER)), + cursor.getString(cursor.getColumnIndexOrThrow(STATIONS.PHOTOGRAPHER_URL)), + cursor.getString(cursor.getColumnIndexOrThrow(STATIONS.LICENSE)), + cursor.getString(cursor.getColumnIndexOrThrow(STATIONS.LICENSE_URL)), + java.lang.Boolean.TRUE == getBoolean(cursor, STATIONS.ACTIVE), + java.lang.Boolean.TRUE == getBoolean(cursor, STATIONS.OUTDATED), + cursor.getLong(cursor.getColumnIndexOrThrow(STATIONS.PHOTO_ID)) + ) + } + + private fun createCountryFromCursor(cursor: Cursor): Country { + return Country( + cursor.getString(cursor.getColumnIndexOrThrow(COUNTRIES.COUNTRYSHORTCODE)), + cursor.getString(cursor.getColumnIndexOrThrow(COUNTRIES.COUNTRYNAME)), + cursor.getString(cursor.getColumnIndexOrThrow(COUNTRIES.EMAIL)), + cursor.getString(cursor.getColumnIndexOrThrow(COUNTRIES.TIMETABLE_URL_TEMPLATE)), + cursor.getString(cursor.getColumnIndexOrThrow(COUNTRIES.OVERRIDE_LICENSE)) + ) + } + + private fun createProviderAppFromCursor(cursor: Cursor): ProviderApp { + return ProviderApp( + cursor.getString(cursor.getColumnIndexOrThrow(PROVIDER_APPS.PA_TYPE)), + cursor.getString(cursor.getColumnIndexOrThrow(PROVIDER_APPS.PA_NAME)), + cursor.getString(cursor.getColumnIndexOrThrow(PROVIDER_APPS.PA_URL)) + ) + } + + private fun createUploadFromCursor(cursor: Cursor): Upload { + val upload = Upload( + cursor.getLong(cursor.getColumnIndexOrThrow(UPLOADS.ID)), + cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.COUNTRY)), + cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.STATION_ID)), + getLong(cursor, UPLOADS.REMOTE_ID), + cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.TITLE)), + getDouble(cursor, UPLOADS.LAT), + getDouble(cursor, UPLOADS.LON), + cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.COMMENT)), + cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.INBOX_URL)), + null, + cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.REJECTED_REASON)), + UploadState.UNKNOWN, + cursor.getLong(cursor.getColumnIndexOrThrow(UPLOADS.CREATED_AT)), + getBoolean(cursor, UPLOADS.ACTIVE), + getLong(cursor, UPLOADS.CRC32) + ) + val problemType = cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.PROBLEM_TYPE)) if (problemType != null) { - upload.setProblemType(ProblemType.valueOf(problemType)); + upload.problemType = ProblemType.valueOf(problemType) } - var uploadState = cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.UPLOAD_STATE)); + val uploadState = cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.UPLOAD_STATE)) if (uploadState != null) { - upload.setUploadState(UploadState.valueOf(uploadState)); + upload.uploadState = UploadState.valueOf(uploadState) } - - return upload; + return upload } - private Boolean getBoolean(Cursor cursor, String columnName) { - if (!cursor.isNull(cursor.getColumnIndexOrThrow(columnName))) { - return cursor.getInt(cursor.getColumnIndexOrThrow(columnName)) == 1; - } - return null; + private fun getBoolean(cursor: Cursor, columnName: String?): Boolean? { + return if (!cursor.isNull(cursor.getColumnIndexOrThrow(columnName))) { + cursor.getInt(cursor.getColumnIndexOrThrow(columnName)) == 1 + } else null } - private Double getDouble(final Cursor cursor, final String columnName) { - if (!cursor.isNull(cursor.getColumnIndexOrThrow(columnName))) { - return cursor.getDouble(cursor.getColumnIndexOrThrow(columnName)); - } - return null; + private fun getDouble(cursor: Cursor, columnName: String?): Double? { + return if (!cursor.isNull(cursor.getColumnIndexOrThrow(columnName))) { + cursor.getDouble(cursor.getColumnIndexOrThrow(columnName)) + } else null } - private Long getLong(final Cursor cursor, final String columnName) { - if (!cursor.isNull(cursor.getColumnIndexOrThrow(columnName))) { - return cursor.getLong(cursor.getColumnIndexOrThrow(columnName)); - } - return null; + private fun getLong(cursor: Cursor, columnName: String?): Long? { + return if (!cursor.isNull(cursor.getColumnIndexOrThrow(columnName))) { + cursor.getLong(cursor.getColumnIndexOrThrow(columnName)) + } else null } - public Station fetchStationByRowId(long id) { - try (var cursor = db.query(DATABASE_TABLE_STATIONS, null, Constants.STATIONS.ROWID + "=?", new String[]{ - id + ""}, null, null, null)) { + fun fetchStationByRowId(id: Long): Station? { + db!!.query( + DATABASE_TABLE_STATIONS, null, STATIONS.ROWID + "=?", arrayOf( + id.toString() + "" + ), null, null, null + ).use { cursor -> if (cursor.moveToFirst()) { - return createStationFromCursor(cursor); + return createStationFromCursor(cursor) } } - return null; + return null } - public Station getStationByKey(String country, String id) { - try (var cursor = db.query(DATABASE_TABLE_STATIONS, null, Constants.STATIONS.COUNTRY + "=? AND " + Constants.STATIONS.ID + "=?", - new String[]{country, id}, null, null, null)) { + fun getStationByKey(country: String?, id: String?): Station? { + db!!.query( + DATABASE_TABLE_STATIONS, + null, + STATIONS.COUNTRY + "=? AND " + STATIONS.ID + "=?", + arrayOf(country, id), + null, + null, + null + ).use { cursor -> if (cursor.moveToFirst()) { - return createStationFromCursor(cursor); + return createStationFromCursor(cursor) } } - return null; + return null } - public Station getStationForUpload(Upload upload) { - try (var cursor = db.query(DATABASE_TABLE_STATIONS, null, Constants.STATIONS.COUNTRY + "=? AND " + Constants.STATIONS.ID + "=?", - new String[]{upload.getCountry(), upload.getStationId()}, null, null, null)) { + fun getStationForUpload(upload: Upload): Station? { + db!!.query( + DATABASE_TABLE_STATIONS, + null, + STATIONS.COUNTRY + "=? AND " + STATIONS.ID + "=?", + arrayOf(upload.country, upload.stationId), + null, + null, + null + ).use { cursor -> if (cursor.moveToFirst()) { - return createStationFromCursor(cursor); + return createStationFromCursor(cursor) } } - return null; - } - - public Set fetchCountriesWithProviderApps(Set countryCodes) { - var countryList = countryCodes.stream() - .map(c -> "'" + c + "'") - .collect(joining(",")); - var countries = new HashSet(); - try (var cursor = db.query(DATABASE_TABLE_COUNTRIES, null, Constants.COUNTRIES.COUNTRYSHORTCODE + " IN (" + countryList + ")", - null, null, null, null)) { + return null + } + + fun fetchCountriesWithProviderApps(countryCodes: Set?): Set { + val countryList = countryCodes!!.stream() + .map { c: String? -> "'$c'" } + .collect(Collectors.joining(",")) + val countries = HashSet() + db!!.query( + DATABASE_TABLE_COUNTRIES, + null, + COUNTRIES.COUNTRYSHORTCODE + " IN (" + countryList + ")", + null, + null, + null, + null + ).use { cursor -> if (cursor != null && cursor.moveToFirst()) { do { - var country = createCountryFromCursor(cursor); - countries.add(country); - try (var cursorPa = db.query(DATABASE_TABLE_PROVIDER_APPS, null, Constants.PROVIDER_APPS.COUNTRYSHORTCODE + " = ?", - new String[]{country.getCode()}, null, null, null)) { + val country = createCountryFromCursor(cursor) + countries.add(country) + db!!.query( + DATABASE_TABLE_PROVIDER_APPS, + null, + PROVIDER_APPS.COUNTRYSHORTCODE + " = ?", + arrayOf(country.code), + null, + null, + null + ).use { cursorPa -> if (cursorPa != null && cursorPa.moveToFirst()) { do { - country.getProviderApps().add(createProviderAppFromCursor(cursorPa)); - } while (cursorPa.moveToNext()); + country.providerApps.add(createProviderAppFromCursor(cursorPa)) + } while (cursorPa.moveToNext()) } } - } while (cursor.moveToNext()); + } while (cursor.moveToNext()) } } - - return countries; + return countries } - public List getAllStations(StationFilter stationFilter, Set countryCodes) { - var stationList = new ArrayList(); - var selectQuery = "SELECT * FROM " + DATABASE_TABLE_STATIONS + " WHERE " + whereCountryCodeIn(countryCodes); - var queryArgs = new ArrayList(); + fun getAllStations(stationFilter: StationFilter?, countryCodes: Set?): List { + val stationList = ArrayList() + var selectQuery = + "SELECT * FROM " + DATABASE_TABLE_STATIONS + " WHERE " + whereCountryCodeIn(countryCodes) + val queryArgs = ArrayList() if (stationFilter.getNickname() != null) { - selectQuery += " AND " + Constants.STATIONS.PHOTOGRAPHER + " = ?"; - queryArgs.add(stationFilter.getNickname()); + selectQuery += " AND " + STATIONS.PHOTOGRAPHER + " = ?" + queryArgs.add(stationFilter.getNickname()) } - if (stationFilter.hasPhoto() != null) { - selectQuery += " AND " + Constants.STATIONS.PHOTO_URL + " IS " + (stationFilter.hasPhoto() ? "NOT" : "") + " NULL"; + if (stationFilter!!.hasPhoto() != null) { + selectQuery += " AND " + STATIONS.PHOTO_URL + " IS " + (if (stationFilter.hasPhoto()!!) "NOT" else "") + " NULL" } - if (stationFilter.isActive() != null) { - selectQuery += " AND " + Constants.STATIONS.ACTIVE + " = ?"; - queryArgs.add(stationFilter.isActive() ? "1" : "0"); + if (stationFilter.isActive != null) { + selectQuery += " AND " + STATIONS.ACTIVE + " = ?" + queryArgs.add(if (stationFilter.isActive) "1" else "0") } - - try (var cursor = db.rawQuery(selectQuery, queryArgs.toArray(new String[]{}))) { + db!!.rawQuery(selectQuery, queryArgs.toArray(arrayOf())).use { cursor -> if (cursor.moveToFirst()) { do { - stationList.add(createStationFromCursor(cursor)); - } while (cursor.moveToNext()); + stationList.add(createStationFromCursor(cursor)) + } while (cursor.moveToNext()) } } - - return stationList; + return stationList } - public List getStationByLatLngRectangle(double lat, double lng, StationFilter stationFilter) { - var stationList = new ArrayList(); + fun getStationByLatLngRectangle( + lat: Double, + lng: Double, + stationFilter: StationFilter? + ): List { + val stationList = ArrayList() // Select All Query with rectangle - might be later change with it - var selectQuery = "SELECT * FROM " + DATABASE_TABLE_STATIONS + " WHERE " + Constants.STATIONS.LAT + " < " + (lat + 0.5) + " AND " + Constants.STATIONS.LAT + " > " + (lat - 0.5) - + " AND " + Constants.STATIONS.LON + " < " + (lng + 0.5) + " AND " + Constants.STATIONS.LON + " > " + (lng - 0.5); - - var queryArgs = new ArrayList(); + var selectQuery = + ("SELECT * FROM " + DATABASE_TABLE_STATIONS + " WHERE " + STATIONS.LAT + " < " + (lat + 0.5) + " AND " + STATIONS.LAT + " > " + (lat - 0.5) + + " AND " + STATIONS.LON + " < " + (lng + 0.5) + " AND " + STATIONS.LON + " > " + (lng - 0.5)) + val queryArgs = ArrayList() if (stationFilter.getNickname() != null) { - selectQuery += " AND " + Constants.STATIONS.PHOTOGRAPHER + " = ?"; - queryArgs.add(stationFilter.getNickname()); + selectQuery += " AND " + STATIONS.PHOTOGRAPHER + " = ?" + queryArgs.add(stationFilter.getNickname()) } - if (stationFilter.hasPhoto() != null) { - selectQuery += " AND " + Constants.STATIONS.PHOTO_URL + " IS " + (stationFilter.hasPhoto() ? "NOT" : "") + " NULL"; + if (stationFilter!!.hasPhoto() != null) { + selectQuery += " AND " + STATIONS.PHOTO_URL + " IS " + (if (stationFilter.hasPhoto()!!) "NOT" else "") + " NULL" } - if (stationFilter.isActive() != null) { - selectQuery += " AND " + Constants.STATIONS.ACTIVE + " = ?"; - queryArgs.add(stationFilter.isActive() ? "1" : "0"); + if (stationFilter.isActive != null) { + selectQuery += " AND " + STATIONS.ACTIVE + " = ?" + queryArgs.add(if (stationFilter.isActive) "1" else "0") } - - try (var cursor = db.rawQuery(selectQuery, queryArgs.toArray(new String[]{}))) { + db!!.rawQuery(selectQuery, queryArgs.toArray(arrayOf())).use { cursor -> if (cursor.moveToFirst()) { do { - stationList.add(createStationFromCursor(cursor)); - } while (cursor.moveToNext()); + stationList.add(createStationFromCursor(cursor)) + } while (cursor.moveToNext()) } } - - return stationList; + return stationList } - public Set getAllCountries() { - var countryList = new HashSet(); - var query = "SELECT * FROM " + DATABASE_TABLE_COUNTRIES; - - Log.d(TAG, query); - try (var cursor = db.rawQuery(query, null)) { - if (cursor.moveToFirst()) { - do { - countryList.add(createCountryFromCursor(cursor)); - } while (cursor.moveToNext()); + val allCountries: Set + get() { + val countryList = HashSet() + val query = "SELECT * FROM " + DATABASE_TABLE_COUNTRIES + Log.d(TAG, query) + db!!.rawQuery(query, null).use { cursor -> + if (cursor.moveToFirst()) { + do { + countryList.add(createCountryFromCursor(cursor)) + } while (cursor.moveToNext()) + } } - } - - return countryList; - } - -} + return countryList + } + + companion object { + private val TAG = DbAdapter::class.java.simpleName + private const val DATABASE_TABLE_STATIONS = "bahnhoefe" + private const val DATABASE_TABLE_COUNTRIES = "laender" + private const val DATABASE_TABLE_PROVIDER_APPS = "providerApps" + private const val DATABASE_TABLE_UPLOADS = "uploads" + private const val DATABASE_NAME = "bahnhoefe.db" + private const val DATABASE_VERSION = 22 + private const val CREATE_STATEMENT_STATIONS = + ("CREATE TABLE " + DATABASE_TABLE_STATIONS + " (" + + STATIONS.ROWID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + STATIONS.COUNTRY + " TEXT, " + + STATIONS.ID + " TEXT, " + + STATIONS.TITLE + " TEXT, " + + STATIONS.NORMALIZED_TITLE + " TEXT, " + + STATIONS.LAT + " REAL, " + + STATIONS.LON + " REAL, " + + STATIONS.PHOTO_ID + " INTEGER, " + + STATIONS.PHOTO_URL + " TEXT, " + + STATIONS.PHOTOGRAPHER + " TEXT, " + + STATIONS.PHOTOGRAPHER_URL + " TEXT, " + + STATIONS.LICENSE + " TEXT, " + + STATIONS.LICENSE_URL + " TEXT, " + + STATIONS.DS100 + " TEXT, " + + STATIONS.ACTIVE + " INTEGER, " + + STATIONS.OUTDATED + " INTEGER)") + private const val CREATE_STATEMENT_STATIONS_IDX = + ("CREATE INDEX " + DATABASE_TABLE_STATIONS + "_IDX " + + "ON " + DATABASE_TABLE_STATIONS + "(" + STATIONS.COUNTRY + ", " + STATIONS.ID + ")") + private const val CREATE_STATEMENT_COUNTRIES = + ("CREATE TABLE " + DATABASE_TABLE_COUNTRIES + " (" + + COUNTRIES.ROWID_COUNTRIES + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + COUNTRIES.COUNTRYSHORTCODE + " TEXT, " + + COUNTRIES.COUNTRYNAME + " TEXT, " + + COUNTRIES.EMAIL + " TEXT, " + + COUNTRIES.TIMETABLE_URL_TEMPLATE + " TEXT, " + + COUNTRIES.OVERRIDE_LICENSE + " TEXT)") + private const val CREATE_STATEMENT_PROVIDER_APPS = + ("CREATE TABLE " + DATABASE_TABLE_PROVIDER_APPS + " (" + + PROVIDER_APPS.COUNTRYSHORTCODE + " TEXT," + + PROVIDER_APPS.PA_TYPE + " TEXT," + + PROVIDER_APPS.PA_NAME + " TEXT, " + + PROVIDER_APPS.PA_URL + " TEXT)") + private const val CREATE_STATEMENT_UPLOADS = + ("CREATE TABLE " + DATABASE_TABLE_UPLOADS + " (" + + UPLOADS.ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + UPLOADS.STATION_ID + " TEXT, " + + UPLOADS.COUNTRY + " TEXT, " + + UPLOADS.REMOTE_ID + " INTEGER, " + + UPLOADS.TITLE + " TEXT, " + + UPLOADS.LAT + " REAL, " + + UPLOADS.LON + " REAL, " + + UPLOADS.COMMENT + " TEXT, " + + UPLOADS.INBOX_URL + " TEXT, " + + UPLOADS.PROBLEM_TYPE + " TEXT, " + + UPLOADS.REJECTED_REASON + " TEXT, " + + UPLOADS.UPLOAD_STATE + " TEXT, " + + UPLOADS.CREATED_AT + " INTEGER, " + + UPLOADS.ACTIVE + " INTEGER, " + + UPLOADS.CRC32 + " INTEGER)") + private const val DROP_STATEMENT_STATIONS_IDX = + "DROP INDEX IF EXISTS " + DATABASE_TABLE_STATIONS + "_IDX" + private const val DROP_STATEMENT_STATIONS = + "DROP TABLE IF EXISTS " + DATABASE_TABLE_STATIONS + private const val DROP_STATEMENT_COUNTRIES = + "DROP TABLE IF EXISTS " + DATABASE_TABLE_COUNTRIES + private const val DROP_STATEMENT_PROVIDER_APPS = + "DROP TABLE IF EXISTS " + DATABASE_TABLE_PROVIDER_APPS + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/HighScoreAdapter.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/HighScoreAdapter.kt index cba4f629..6f1dd382 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/HighScoreAdapter.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/HighScoreAdapter.kt @@ -1,127 +1,106 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.db; - -import android.app.Activity; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.Filter; - -import androidx.annotation.NonNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ItemHighscoreBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.HighScoreItem; - -public class HighScoreAdapter extends ArrayAdapter { - private final Activity context; - private List highScore; - private HighScoreFilter filter; - - public HighScoreAdapter(Activity context, List highScore) { - super(context, R.layout.item_highscore, highScore); - this.highScore = highScore; - this.context = context; - } - - @Override - @NonNull - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - var rowView = convertView; +package de.bahnhoefe.deutschlands.bahnhofsfotos.db + +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.Filter +import de.bahnhoefe.deutschlands.bahnhofsfotos.R +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ItemHighscoreBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.HighScoreItem +import java.util.Locale + +class HighScoreAdapter(private val context: Activity, private var highScore: List) : + ArrayAdapter( + context, R.layout.item_highscore, highScore + ) { + private var filter: HighScoreFilter? = null + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + var rowView = convertView // reuse views - ItemHighscoreBinding binding; + val binding: ItemHighscoreBinding if (rowView == null) { - binding = ItemHighscoreBinding.inflate(context.getLayoutInflater(), parent, false); - rowView = binding.getRoot(); - rowView.setTag(binding); + binding = ItemHighscoreBinding.inflate( + context.layoutInflater, parent, false + ) + rowView = binding.root + rowView.setTag(binding) } else { - binding = (ItemHighscoreBinding) rowView.getTag(); + binding = rowView.tag as ItemHighscoreBinding } + val (name, photos, position1) = highScore[position] + binding.highscoreName.text = name + binding.highscorePhotos.text = photos.toString() + binding.highscorePosition.text = "$position1." + when (position1) { + 1 -> { + binding.highscoreAward.setImageResource(R.drawable.ic_crown_gold) + binding.highscoreAward.visibility = View.VISIBLE + binding.highscorePosition.visibility = View.GONE + } + + 2 -> { + binding.highscoreAward.setImageResource(R.drawable.ic_crown_silver) + binding.highscoreAward.visibility = View.VISIBLE + binding.highscorePosition.visibility = View.GONE + } - var item = highScore.get(position); - binding.highscoreName.setText(item.getName()); - binding.highscorePhotos.setText(String.valueOf(item.getPhotos())); - binding.highscorePosition.setText(String.valueOf(item.getPosition()).concat(".")); + 3 -> { + binding.highscoreAward.setImageResource(R.drawable.ic_crown_bronze) + binding.highscoreAward.visibility = View.VISIBLE + binding.highscorePosition.visibility = View.GONE + } - switch (item.getPosition()) { - case 1: - binding.highscoreAward.setImageResource(R.drawable.ic_crown_gold); - binding.highscoreAward.setVisibility(View.VISIBLE); - binding.highscorePosition.setVisibility(View.GONE); - break; - case 2: - binding.highscoreAward.setImageResource(R.drawable.ic_crown_silver); - binding.highscoreAward.setVisibility(View.VISIBLE); - binding.highscorePosition.setVisibility(View.GONE); - break; - case 3: - binding.highscoreAward.setImageResource(R.drawable.ic_crown_bronze); - binding.highscoreAward.setVisibility(View.VISIBLE); - binding.highscorePosition.setVisibility(View.GONE); - break; - default: - binding.highscoreAward.setVisibility(View.GONE); - binding.highscorePosition.setVisibility(View.VISIBLE); - break; + else -> { + binding.highscoreAward.visibility = View.GONE + binding.highscorePosition.visibility = View.VISIBLE + } } - if (position % 2 == 1) { - rowView.setBackgroundResource(R.drawable.item_list_backgroundcolor); + rowView.setBackgroundResource(R.drawable.item_list_backgroundcolor) } else { - rowView.setBackgroundResource(R.drawable.item_list_backgroundcolor2); + rowView.setBackgroundResource(R.drawable.item_list_backgroundcolor2) } - - return rowView; + return rowView } - @NonNull - @Override - public Filter getFilter() { + override fun getFilter(): Filter { if (filter == null) { - filter = new HighScoreFilter(highScore); + filter = HighScoreFilter(highScore) } - return filter; + return filter!! } - private class HighScoreFilter extends Filter { + private inner class HighScoreFilter(originalItems: List) : Filter() { + private val originalItems: MutableList = ArrayList() - private final List originalItems = new ArrayList<>(); - - public HighScoreFilter(List originalItems) { - this.originalItems.addAll(originalItems); + init { + this.originalItems.addAll(originalItems) } - @Override - protected FilterResults performFiltering(CharSequence constraint) { - var filterResults = new FilterResults(); - - if (constraint != null) { - var search = constraint.toString().toLowerCase(); - var tempList = originalItems.stream() - .filter(item -> item.getName().toLowerCase().contains(search)) - .collect(Collectors.toList()); - - filterResults.values = tempList; - filterResults.count = tempList.size(); - } - return filterResults; + override fun performFiltering(constraint: CharSequence): FilterResults { + val filterResults = FilterResults() + val search = constraint.toString().lowercase(Locale.getDefault()) + val tempList = originalItems + .filter { (name): HighScoreItem -> + name!!.lowercase(Locale.getDefault()).contains(search) + } + .toList() + filterResults.values = tempList + filterResults.count = tempList.size + return filterResults } - @SuppressWarnings("unchecked") - @Override - protected void publishResults(CharSequence contraint, FilterResults results) { - highScore = (ArrayList) results.values; - clear(); - addAll(highScore); + override fun publishResults(contraint: CharSequence, results: FilterResults) { + @Suppress("UNCHECKED_CAST") + highScore = results.values as List + clear() + addAll(highScore) if (results.count > 0) { - notifyDataSetChanged(); + notifyDataSetChanged() } else { - notifyDataSetInvalidated(); + notifyDataSetInvalidated() } } } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/InboxAdapter.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/InboxAdapter.kt index aa74a546..450570b6 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/InboxAdapter.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/InboxAdapter.kt @@ -1,53 +1,40 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.db; - -import android.app.Activity; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; - -import androidx.annotation.NonNull; - -import java.util.List; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ItemInboxBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PublicInbox; - -public class InboxAdapter extends ArrayAdapter { - private final Activity context; - private final List publicInboxes; - - public InboxAdapter(Activity context, List publicInboxes) { - super(context, R.layout.item_inbox, publicInboxes); - this.publicInboxes = publicInboxes; - this.context = context; - } - - @Override - @NonNull - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - var rowView = convertView; +package de.bahnhoefe.deutschlands.bahnhofsfotos.db + +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import de.bahnhoefe.deutschlands.bahnhofsfotos.R +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ItemInboxBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PublicInbox + +class InboxAdapter(private val context: Activity, private val publicInboxes: List) : + ArrayAdapter( + context, R.layout.item_inbox, publicInboxes + ) { + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + var rowView = convertView // reuse views - ItemInboxBinding binding; + val binding: ItemInboxBinding if (rowView == null) { - binding = ItemInboxBinding.inflate(context.getLayoutInflater(), parent, false); - rowView = binding.getRoot(); - rowView.setTag(binding); + binding = ItemInboxBinding.inflate( + context.layoutInflater, parent, false + ) + rowView = binding.root + rowView.setTag(binding) } else { - binding = (ItemInboxBinding) rowView.getTag(); + binding = rowView.tag as ItemInboxBinding } // fill data - var item = publicInboxes.get(position); - binding.txtStationName.setText(item.getTitle()); - if (item.getStationId() != null) { - binding.txtStationId.setText(item.getCountryCode().concat(":").concat(item.getStationId())); + val (title, countryCode, stationId, lat, lon) = publicInboxes[position] + binding.txtStationName.text = title + if (stationId != null) { + binding.txtStationId.text = "$countryCode:$stationId" } else { - binding.txtStationId.setText(R.string.missing_station); + binding.txtStationId.setText(R.string.missing_station) } - binding.txtCoordinates.setText(String.valueOf(item.getLat()).concat(",").concat(String.valueOf(item.getLon()))); - - return rowView; + binding.txtCoordinates.text = lat.toString() + "," + lon.toString() + return rowView } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/OutboxAdapter.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/OutboxAdapter.kt index 964c7a93..917ac9e8 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/OutboxAdapter.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/OutboxAdapter.kt @@ -1,77 +1,85 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.db; +package de.bahnhoefe.deutschlands.bahnhofsfotos.db -import android.app.Activity; -import android.content.Context; -import android.database.Cursor; -import android.graphics.BitmapFactory; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorAdapter; +import android.app.Activity +import android.content.Context +import android.database.Cursor +import android.graphics.BitmapFactory +import android.view.View +import android.view.ViewGroup +import android.widget.CursorAdapter +import androidx.appcompat.content.res.AppCompatResources +import de.bahnhoefe.deutschlands.bahnhofsfotos.R +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ItemUploadBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProblemType +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.UploadState +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants.UPLOADS +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.FileUtils -import androidx.appcompat.content.res.AppCompatResources; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ItemUploadBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProblemType; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.UploadState; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.FileUtils; - -public class OutboxAdapter extends CursorAdapter { - - private final Activity activity; - - public OutboxAdapter(Activity activity, Cursor uploadCursor) { - super(activity, uploadCursor, 0); - this.activity = activity; - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - var binding = ItemUploadBinding.inflate(activity.getLayoutInflater(), parent, false); - var view = binding.getRoot(); - view.setTag(binding); - return view; +class OutboxAdapter(private val activity: Activity, uploadCursor: Cursor?) : CursorAdapter( + activity, uploadCursor, 0 +) { + override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View { + val binding = ItemUploadBinding.inflate( + activity.layoutInflater, parent, false + ) + val view = binding.root + view.tag = binding + return view } - @Override - public void bindView(View view, Context context, Cursor cursor) { - var binding = (ItemUploadBinding) view.getTag(); - var id = cursor.getLong(cursor.getColumnIndexOrThrow(Constants.CURSOR_ADAPTER_ID)); - var remoteId = cursor.getLong(cursor.getColumnIndexOrThrow(Constants.UPLOADS.REMOTE_ID)); - var stationId = cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.STATION_ID)); - var uploadTitle = cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.TITLE)); - var stationTitle = cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.JOIN_STATION_TITLE)); - var problemType = cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.PROBLEM_TYPE)); - var uploadStateStr = cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.UPLOAD_STATE)); - var comment = cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.COMMENT)); - var rejectReason = cursor.getString(cursor.getColumnIndexOrThrow(Constants.UPLOADS.REJECTED_REASON)); - - var uploadState = UploadState.valueOf(uploadStateStr); - var textState = id + (remoteId > 0 ? "/" + remoteId : "") + ": " + context.getString(uploadState.getTextId()); - binding.txtState.setText(textState); - binding.txtState.setTextColor(context.getResources().getColor(uploadState.getColorId(), null)); - binding.uploadPhoto.setImageBitmap(null); - binding.uploadPhoto.setVisibility(View.GONE); - + override fun bindView(view: View, context: Context, cursor: Cursor) { + val binding = view.tag as ItemUploadBinding + val id = cursor.getLong(cursor.getColumnIndexOrThrow(Constants.CURSOR_ADAPTER_ID)) + val remoteId = cursor.getLong(cursor.getColumnIndexOrThrow(UPLOADS.REMOTE_ID)) + val stationId = cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.STATION_ID)) + val uploadTitle = cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.TITLE)) + val stationTitle = + cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.JOIN_STATION_TITLE)) + val problemType = cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.PROBLEM_TYPE)) + val uploadStateStr = cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.UPLOAD_STATE)) + val comment = cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.COMMENT)) + val rejectReason = cursor.getString(cursor.getColumnIndexOrThrow(UPLOADS.REJECTED_REASON)) + val uploadState = UploadState.valueOf(uploadStateStr) + val textState = + id.toString() + (if (remoteId > 0) "/$remoteId" else "") + ": " + context.getString( + uploadState.textId + ) + binding.txtState.text = textState + binding.txtState.setTextColor(context.resources.getColor(uploadState.colorId, null)) + binding.uploadPhoto.setImageBitmap(null) + binding.uploadPhoto.visibility = View.GONE if (problemType != null) { - binding.uploadType.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_bullhorn_red_48px)); - binding.txtComment.setText(String.format("%s: %s", context.getText(ProblemType.valueOf(problemType).getMessageId()), comment)); + binding.uploadType.setImageDrawable( + AppCompatResources.getDrawable( + context, + R.drawable.ic_bullhorn_red_48px + ) + ) + binding.txtComment.text = String.format( + "%s: %s", + context.getText(ProblemType.valueOf(problemType).messageId), + comment + ) } else { - binding.uploadType.setImageDrawable(AppCompatResources.getDrawable(context, stationId == null ? R.drawable.ic_station_red_24px : R.drawable.ic_photo_red_48px)); - binding.txtComment.setText(comment); - var file = FileUtils.getStoredMediaFile(context, id); + binding.uploadType.setImageDrawable( + AppCompatResources.getDrawable( + context, + if (stationId == null) R.drawable.ic_station_red_24px else R.drawable.ic_photo_red_48px + ) + ) + binding.txtComment.text = comment + val file = FileUtils.getStoredMediaFile(context, id) if (file != null) { - var bitmap = BitmapFactory.decodeFile(file.getAbsolutePath()); - binding.uploadPhoto.setImageBitmap(bitmap); - binding.uploadPhoto.setVisibility(View.VISIBLE); + val bitmap = BitmapFactory.decodeFile(file.absolutePath) + binding.uploadPhoto.setImageBitmap(bitmap) + binding.uploadPhoto.visibility = View.VISIBLE } } - binding.txtComment.setVisibility(comment == null ? View.GONE : View.VISIBLE); - - binding.txtStationName.setText(uploadTitle != null ? uploadTitle : stationTitle); - binding.txtRejectReason.setText(rejectReason); - binding.txtRejectReason.setVisibility(rejectReason == null ? View.GONE : View.VISIBLE); + binding.txtComment.visibility = + if (comment == null) View.GONE else View.VISIBLE + binding.txtStationName.text = uploadTitle ?: stationTitle + binding.txtRejectReason.text = rejectReason + binding.txtRejectReason.visibility = if (rejectReason == null) View.GONE else View.VISIBLE } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/StationListAdapter.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/StationListAdapter.kt index 5ca474d2..b5e13ff6 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/StationListAdapter.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/db/StationListAdapter.kt @@ -1,45 +1,46 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.db; - -import android.content.Context; -import android.database.Cursor; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorAdapter; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ItemStationBinding; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants; - -public class StationListAdapter extends CursorAdapter { - private final LayoutInflater mInflater; - - public StationListAdapter(Context context, Cursor cursor, int flags) { - super(context, cursor, flags); - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); +package de.bahnhoefe.deutschlands.bahnhofsfotos.db + +import android.content.Context +import android.database.Cursor +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CursorAdapter +import de.bahnhoefe.deutschlands.bahnhofsfotos.R +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.ItemStationBinding +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Constants.STATIONS + +class StationListAdapter(context: Context, cursor: Cursor?, flags: Int) : + CursorAdapter(context, cursor, flags) { + private val mInflater: LayoutInflater + + init { + mInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater } - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - var binding = ItemStationBinding.inflate(mInflater, parent, false); - var view = binding.getRoot(); - view.setTag(binding); - return view; + override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View { + val binding = ItemStationBinding.inflate(mInflater, parent, false) + val view = binding.root + view.tag = binding + return view } - @Override - public void bindView(View view, Context context, Cursor cursor) { + override fun bindView(view: View, context: Context, cursor: Cursor) { //If you want to have zebra lines color effect uncomment below code - if (cursor.getPosition() % 2 == 1) { - view.setBackgroundResource(R.drawable.item_list_backgroundcolor); + if (cursor.position % 2 == 1) { + view.setBackgroundResource(R.drawable.item_list_backgroundcolor) } else { - view.setBackgroundResource(R.drawable.item_list_backgroundcolor2); + view.setBackgroundResource(R.drawable.item_list_backgroundcolor2) } - - var binding = (ItemStationBinding) view.getTag(); - binding.txtState.setText(cursor.getString(cursor.getColumnIndexOrThrow(Constants.STATIONS.COUNTRY)).concat(": ").concat(cursor.getString(cursor.getColumnIndexOrThrow(Constants.STATIONS.ID)))); - binding.txtStationName.setText(cursor.getString(cursor.getColumnIndexOrThrow(Constants.STATIONS.TITLE))); - binding.hasPhoto.setVisibility(cursor.getString(cursor.getColumnIndexOrThrow(Constants.STATIONS.PHOTO_URL)) != null? View.VISIBLE : View.INVISIBLE); + val binding = view.tag as ItemStationBinding + binding.txtStationKey.text = getStationkey(cursor) + binding.txtStationName.text = cursor.getString(cursor.getColumnIndexOrThrow(STATIONS.TITLE)) + binding.hasPhoto.visibility = + if (cursor.getString(cursor.getColumnIndexOrThrow(STATIONS.PHOTO_URL)) != null) View.VISIBLE else View.INVISIBLE } -} + private fun getStationkey(cursor: Cursor) = + cursor.getString(cursor.getColumnIndexOrThrow(STATIONS.COUNTRY)) + ": " + cursor.getString( + cursor.getColumnIndexOrThrow(STATIONS.ID) + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/AppInfoFragment.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/AppInfoFragment.kt index 6a35c9a0..631d0500 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/AppInfoFragment.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/AppInfoFragment.kt @@ -1,42 +1,35 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs; - -import android.app.Dialog; -import android.graphics.Color; -import android.os.Bundle; -import android.text.Html; -import android.text.method.LinkMovementMethod; -import android.view.ContextThemeWrapper; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.DialogFragment; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.BuildConfig; -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; - -public class AppInfoFragment extends DialogFragment { - - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - var textView = new TextView(getContext()); - textView.setLinksClickable(true); - textView.setTextSize((float) 18); - textView.setPadding(50, 50, 50, 50); - textView.setMovementMethod(LinkMovementMethod.getInstance()); - textView.setText(Html.fromHtml(getString(R.string.app_info_text, BuildConfig.VERSION_NAME),Html.FROM_HTML_MODE_COMPACT)); - textView.setLinkTextColor(Color.parseColor("#c71c4d")); - - return new AlertDialog.Builder(new ContextThemeWrapper(getActivity(), R.style.AlertDialogCustom)) - .setIcon(R.mipmap.ic_launcher) - .setTitle(R.string.app_info_title) - .setPositiveButton(R.string.app_info_ok, (dialog, id) -> { - // noop, just close dialog - }) - .setView(textView) - .create(); +package de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs + +import android.app.Dialog +import android.content.DialogInterface +import android.graphics.Color +import android.os.Bundle +import android.text.Html +import android.text.method.LinkMovementMethod +import android.view.ContextThemeWrapper +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import de.bahnhoefe.deutschlands.bahnhofsfotos.BuildConfig +import de.bahnhoefe.deutschlands.bahnhofsfotos.R + +class AppInfoFragment : DialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val textView = TextView(context) + textView.linksClickable = true + textView.textSize = 18f + textView.setPadding(50, 50, 50, 50) + textView.movementMethod = LinkMovementMethod.getInstance() + textView.text = Html.fromHtml( + getString(R.string.app_info_text, BuildConfig.VERSION_NAME), + Html.FROM_HTML_MODE_COMPACT + ) + textView.setLinkTextColor(Color.parseColor("#c71c4d")) + return AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AlertDialogCustom)) + .setIcon(R.mipmap.ic_launcher) + .setTitle(R.string.app_info_title) + .setPositiveButton(R.string.app_info_ok) { _: DialogInterface?, _: Int -> } + .setView(textView) + .create() } - - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/MapInfoFragment.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/MapInfoFragment.kt index 0461260f..af86b56d 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/MapInfoFragment.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/MapInfoFragment.kt @@ -1,40 +1,29 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs; - -import android.app.Dialog; -import android.graphics.Color; -import android.os.Bundle; -import android.text.method.LinkMovementMethod; -import android.view.ContextThemeWrapper; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.DialogFragment; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; - -public class MapInfoFragment extends DialogFragment { - - - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - var textView = new TextView(getContext()); - textView.setTextSize((float) 18); - textView.setPadding(50, 50, 50, 50); - textView.setMovementMethod(LinkMovementMethod.getInstance()); - textView.setText(R.string.map_info_text); - textView.setLinkTextColor(Color.parseColor("#c71c4d")); - - return new AlertDialog.Builder(new ContextThemeWrapper(getActivity(), R.style.AlertDialogCustom)) - .setIcon(R.mipmap.ic_launcher) - .setTitle(R.string.map_info_title) - .setPositiveButton(R.string.app_info_ok, (dialog, id) -> { - // noop, just close dialog - }) - .setView(textView) - .create(); +package de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs + +import android.app.Dialog +import android.content.DialogInterface +import android.graphics.Color +import android.os.Bundle +import android.text.method.LinkMovementMethod +import android.view.ContextThemeWrapper +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import de.bahnhoefe.deutschlands.bahnhofsfotos.R + +class MapInfoFragment : DialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val textView = TextView(context) + textView.textSize = 18f + textView.setPadding(50, 50, 50, 50) + textView.movementMethod = LinkMovementMethod.getInstance() + textView.setText(R.string.map_info_text) + textView.setLinkTextColor(Color.parseColor("#c71c4d")) + return AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AlertDialogCustom)) + .setIcon(R.mipmap.ic_launcher) + .setTitle(R.string.map_info_title) + .setPositiveButton(R.string.app_info_ok) { _: DialogInterface?, _: Int -> } + .setView(textView) + .create() } - - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/SimpleDialogs.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/SimpleDialogs.kt index 677af6a3..ee3630a6 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/SimpleDialogs.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/SimpleDialogs.kt @@ -1,97 +1,118 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; -import android.widget.ArrayAdapter; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; -import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.PromptBinding; - -public class SimpleDialogs { - - private SimpleDialogs() { +package de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs + +import android.app.AlertDialog +import android.content.Context +import android.content.DialogInterface +import android.view.ContextThemeWrapper +import android.view.LayoutInflater +import android.widget.ArrayAdapter +import de.bahnhoefe.deutschlands.bahnhofsfotos.R +import de.bahnhoefe.deutschlands.bahnhofsfotos.databinding.PromptBinding + +object SimpleDialogs { + fun confirmOk( + context: Context, + message: Int, + listener: DialogInterface.OnClickListener? = null + ) { + AlertDialog.Builder(ContextThemeWrapper(context, R.style.AlertDialogCustom)) + .setIcon(R.mipmap.ic_launcher) + .setTitle(R.string.app_name) + .setMessage(message) + .setNeutralButton(R.string.button_ok_text, listener).create().show() } - public static void confirmOk(Context context, int message) { - confirmOk(context, message, null); + fun confirmOk( + context: Context, + message: CharSequence, + listener: DialogInterface.OnClickListener? = null + ) { + AlertDialog.Builder(ContextThemeWrapper(context, R.style.AlertDialogCustom)) + .setIcon(R.mipmap.ic_launcher) + .setTitle(R.string.app_name) + .setMessage(message) + .setNeutralButton(R.string.button_ok_text, listener).create().show() } - public static void confirmOk(Context context, int message, DialogInterface.OnClickListener listener) { - new AlertDialog.Builder(new ContextThemeWrapper(context, R.style.AlertDialogCustom)) - .setIcon(R.mipmap.ic_launcher) - .setTitle(R.string.app_name) - .setMessage(message) - .setNeutralButton(R.string.button_ok_text, listener).create().show(); + fun confirmOkCancel( + context: Context, + message: Int, + listener: DialogInterface.OnClickListener? + ) { + AlertDialog.Builder(ContextThemeWrapper(context, R.style.AlertDialogCustom)) + .setIcon(R.mipmap.ic_launcher) + .setTitle(R.string.app_name) + .setMessage(message) + .setPositiveButton(R.string.button_ok_text, listener) + .setNegativeButton(R.string.button_cancel_text, null) + .create().show() } - public static void confirmOk(Context context, CharSequence message) { - confirmOk(context, message, null); + fun confirmOkCancel( + context: Context, + message: String, + listener: DialogInterface.OnClickListener? + ) { + AlertDialog.Builder(ContextThemeWrapper(context, R.style.AlertDialogCustom)) + .setIcon(R.mipmap.ic_launcher) + .setTitle(R.string.app_name) + .setMessage(message) + .setPositiveButton(R.string.button_ok_text, listener) + .setNegativeButton(R.string.button_cancel_text, null) + .create().show() } - public static void confirmOk(Context context, CharSequence message, DialogInterface.OnClickListener listener) { - new AlertDialog.Builder(new ContextThemeWrapper(context, R.style.AlertDialogCustom)) - .setIcon(R.mipmap.ic_launcher) - .setTitle(R.string.app_name) - .setMessage(message) - .setNeutralButton(R.string.button_ok_text, listener).create().show(); + fun simpleSelect( + context: Context, + message: CharSequence, + items: Array, + listener: DialogInterface.OnClickListener? + ) { + AlertDialog.Builder(ContextThemeWrapper(context, R.style.AlertDialogCustom)) + .setIcon(R.mipmap.ic_launcher) + .setTitle(message) + .setAdapter( + ArrayAdapter(context, android.R.layout.simple_list_item_1, 0, items), + listener + ) + .create().show() } - public static void confirmOkCancel(Context context, int message, DialogInterface.OnClickListener listener) { - new AlertDialog.Builder(new ContextThemeWrapper(context, R.style.AlertDialogCustom)) - .setIcon(R.mipmap.ic_launcher) - .setTitle(R.string.app_name) - .setMessage(message) - .setPositiveButton(R.string.button_ok_text, listener) - .setNegativeButton(R.string.button_cancel_text, null) - .create().show(); - } - - public static void confirmOkCancel(Context context, String message, DialogInterface.OnClickListener listener) { - new AlertDialog.Builder(new ContextThemeWrapper(context, R.style.AlertDialogCustom)) - .setIcon(R.mipmap.ic_launcher) - .setTitle(R.string.app_name) - .setMessage(message) - .setPositiveButton(R.string.button_ok_text, listener) - .setNegativeButton(R.string.button_cancel_text, null) - .create().show(); - } - - public static void simpleSelect(Context context, CharSequence message, CharSequence[] items, DialogInterface.OnClickListener listener) { - new AlertDialog.Builder(new ContextThemeWrapper(context, R.style.AlertDialogCustom)) - .setIcon(R.mipmap.ic_launcher) - .setTitle(message) - .setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, 0, items), listener) - .create().show(); - } - - public static void prompt(Context context, int message, int inputType, int hint, String text, PromptListener listener) { - var binding = PromptBinding.inflate(LayoutInflater.from(context)); - if (text != null) { - binding.etPrompt.setText(text); + fun prompt( + context: Context, + message: Int, + inputType: Int, + hint: Int, + text: String?, + listener: (String?) -> Unit + ) { + val binding = PromptBinding.inflate(LayoutInflater.from(context)) + text?.let { + binding.etPrompt.setText(it) } - binding.etPrompt.setHint(hint); - binding.etPrompt.setInputType(inputType); - - var alertDialog = new androidx.appcompat.app.AlertDialog.Builder(new ContextThemeWrapper(context, R.style.AlertDialogCustom)) - .setTitle(message) - .setView(binding.getRoot()) - .setIcon(R.mipmap.ic_launcher) - .setPositiveButton(android.R.string.ok, null) - .setNegativeButton(android.R.string.cancel, (dialog, id1) -> dialog.cancel()) - .create(); - - alertDialog.show(); - alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> { - alertDialog.dismiss(); - listener.prompt(binding.etPrompt.getText().toString()); - }); + binding.etPrompt.setHint(hint) + binding.etPrompt.inputType = inputType + val alertDialog = androidx.appcompat.app.AlertDialog.Builder( + ContextThemeWrapper( + context, + R.style.AlertDialogCustom + ) + ) + .setTitle(message) + .setView(binding.root) + .setIcon(R.mipmap.ic_launcher) + .setPositiveButton(android.R.string.ok, null) + .setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _: Int -> dialog.cancel() } + .create() + alertDialog.show() + alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE) + .setOnClickListener { + alertDialog.dismiss() + listener.prompt(binding.etPrompt.text.toString()) + } } - public interface PromptListener { - void prompt(String prompt); + interface PromptListener { + fun prompt(prompt: String?) } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/StationFilterBar.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/StationFilterBar.kt index 3bf118ca..e3c2d963 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/StationFilterBar.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/dialogs/StationFilterBar.kt @@ -1,308 +1,372 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Context; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.InsetDrawable; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Log; -import android.util.TypedValue; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.Toast; - -import androidx.annotation.Nullable; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.appcompat.view.menu.MenuBuilder; -import androidx.appcompat.widget.PopupMenu; -import androidx.core.content.ContextCompat; -import androidx.core.graphics.drawable.DrawableCompat; - -import com.google.android.material.chip.Chip; - -import java.util.stream.IntStream; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.BaseApplication; -import de.bahnhoefe.deutschlands.bahnhofsfotos.CountryActivity; -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.StationFilter; - -public class StationFilterBar extends LinearLayout { - - private static final String TAG = StationFilterBar.class.getSimpleName(); - - private final Chip toggleSort; - private final Chip photoFilter; - private final Chip activeFilter; - private final Chip nicknameFilter; - private final Chip countrySelection; - private OnChangeListener listener; - private BaseApplication baseApplication; - private Activity activity; - - public StationFilterBar(Context context) { - this(context, null); - } - - public StationFilterBar(Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } - - public StationFilterBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public StationFilterBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - LayoutInflater.from(context).inflate(R.layout.station_filter_bar, this); - - toggleSort = findViewById(R.id.toggleSort); - toggleSort.setOnClickListener(this::showSortMenu); - - photoFilter = findViewById(R.id.photoFilter); - photoFilter.setOnClickListener(this::showPhotoFilter); - - activeFilter = findViewById(R.id.activeFilter); - activeFilter.setOnClickListener(this::showActiveFilter); - - nicknameFilter = findViewById(R.id.nicknameFilter); - nicknameFilter.setOnClickListener(this::selectNicknameFilter); - - countrySelection = findViewById(R.id.countrySelection); - countrySelection.setOnClickListener(this::selectCountry); +package de.bahnhoefe.deutschlands.bahnhofsfotos.dialogs + +import android.annotation.SuppressLint +import android.app.Activity +import android.app.AlertDialog +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.graphics.drawable.Drawable +import android.graphics.drawable.InsetDrawable +import android.text.TextUtils +import android.util.AttributeSet +import android.util.Log +import android.util.TypedValue +import android.view.ContextThemeWrapper +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.widget.LinearLayout +import android.widget.Toast +import androidx.appcompat.content.res.AppCompatResources +import androidx.appcompat.view.menu.MenuBuilder +import androidx.appcompat.widget.PopupMenu +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat +import com.google.android.material.chip.Chip +import de.bahnhoefe.deutschlands.bahnhofsfotos.BaseApplication +import de.bahnhoefe.deutschlands.bahnhofsfotos.CountryActivity +import de.bahnhoefe.deutschlands.bahnhofsfotos.R +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.StationFilter +import java.util.stream.IntStream + +class StationFilterBar( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int +) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) { + private val toggleSort: Chip + private val photoFilter: Chip + private val activeFilter: Chip + private val nicknameFilter: Chip + private val countrySelection: Chip + private var listener: OnChangeListener? = null + private lateinit var baseApplication: BaseApplication + private lateinit var activity: Activity + + @JvmOverloads + constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : this( + context, + attrs, + defStyleAttr, + 0 + ) + + init { + LayoutInflater.from(context).inflate(R.layout.station_filter_bar, this) + toggleSort = findViewById(R.id.toggleSort) + toggleSort.setOnClickListener { v: View -> showSortMenu(v) } + photoFilter = findViewById(R.id.photoFilter) + photoFilter.setOnClickListener { v: View -> showPhotoFilter(v) } + activeFilter = findViewById(R.id.activeFilter) + activeFilter.setOnClickListener { v: View -> showActiveFilter(v) } + nicknameFilter = findViewById(R.id.nicknameFilter) + nicknameFilter.setOnClickListener { selectNicknameFilter() } + countrySelection = findViewById(R.id.countrySelection) + countrySelection.setOnClickListener { selectCountry() } } - private void setCloseIcon(final Chip chip, final int icon) { - chip.setCloseIcon(AppCompatResources.getDrawable(this.baseApplication, icon)); + private fun setCloseIcon(chip: Chip, icon: Int) { + chip.closeIcon = AppCompatResources.getDrawable(baseApplication, icon) } - private void setChipStatus(Chip chip, int iconRes, boolean active, int textRes) { - setChipStatus(chip, iconRes, active, baseApplication.getString(textRes)); + private fun setChipStatus(chip: Chip, iconRes: Int, active: Boolean, textRes: Int) { + setChipStatus(chip, iconRes, active, baseApplication.getString(textRes)) } - private void setChipStatus(Chip chip, int iconRes, boolean active, String text) { + private fun setChipStatus(chip: Chip, iconRes: Int, active: Boolean, text: String?) { if (iconRes != 0) { - chip.setChipIcon(getTintedDrawable(this.baseApplication, iconRes, getChipForegroundColor(active))); + chip.chipIcon = + getTintedDrawable(baseApplication, iconRes, getChipForegroundColor(active)) } else { - chip.setChipIcon(null); + chip.chipIcon = null } - chip.setChipBackgroundColorResource(active ? R.color.colorPrimary : R.color.fullTransparent); - chip.setTextColor(getChipForegroundColor(active)); - chip.setCloseIconTintResource(getChipForegroundColorRes(active)); - chip.setChipStrokeColorResource(active ? R.color.colorPrimary : R.color.chipForeground); - chip.setText(text); - chip.setTextEndPadding(0); + chip.setChipBackgroundColorResource(if (active) R.color.colorPrimary else R.color.fullTransparent) + chip.setTextColor(getChipForegroundColor(active)) + chip.setCloseIconTintResource(getChipForegroundColorRes(active)) + chip.setChipStrokeColorResource(if (active) R.color.colorPrimary else R.color.chipForeground) + chip.text = text + chip.textEndPadding = 0f if (TextUtils.isEmpty(text)) { - chip.setTextStartPadding(0); + chip.textStartPadding = 0f } else { - chip.setTextStartPadding(activity.getResources().getDimension(R.dimen.chip_textStartPadding)); + chip.textStartPadding = + activity.resources.getDimension(R.dimen.chip_textStartPadding) } } - private Drawable getTintedDrawable(Context context, int imageId, int color) { + private fun getTintedDrawable(context: Context, imageId: Int, color: Int): Drawable? { if (imageId > 0) { - var unwrappedDrawable = ContextCompat.getDrawable(context, imageId); - return getTintedDrawable(unwrappedDrawable, color); + val unwrappedDrawable = ContextCompat.getDrawable(context, imageId) + return getTintedDrawable(unwrappedDrawable, color) } - return null; + return null } - @Nullable - private static Drawable getTintedDrawable(Drawable unwrappedDrawable, int color) { - if (unwrappedDrawable != null) { - var wrappedDrawable = DrawableCompat.wrap(unwrappedDrawable); - DrawableCompat.setTint(wrappedDrawable, color); - return wrappedDrawable; - } - return null; - } - - private int getChipForegroundColor(final boolean active) { - return this.baseApplication.getColor(getChipForegroundColorRes(active)); + private fun getChipForegroundColor(active: Boolean): Int { + return baseApplication.getColor(getChipForegroundColorRes(active)) } - private int getChipForegroundColorRes(final boolean active) { - return active ? R.color.colorOnPrimary : R.color.chipForeground; + private fun getChipForegroundColorRes(active: Boolean): Int { + return if (active) R.color.colorOnPrimary else R.color.chipForeground } - public void init(BaseApplication baseApplication, Activity activity) { - this.baseApplication = baseApplication; - this.activity = activity; - if (activity instanceof OnChangeListener onChangeListener) { - listener = onChangeListener; + fun init(baseApplication: BaseApplication, activity: Activity) { + this.baseApplication = baseApplication + this.activity = activity + if (activity is OnChangeListener) { + listener = activity } - var stationFilter = baseApplication.getStationFilter(); - - setChipStatus(photoFilter, stationFilter.getPhotoIcon(), stationFilter.isPhotoFilterActive(), R.string.no_text); - setChipStatus(nicknameFilter, stationFilter.getNicknameIcon(), stationFilter.isNicknameFilterActive(), stationFilter.getNicknameText(this.baseApplication)); - setChipStatus(activeFilter, stationFilter.getActiveIcon(), stationFilter.isActiveFilterActive(), stationFilter.getActiveText()); - setChipStatus(countrySelection, R.drawable.ic_countries_active_24px, true, getCountryText(baseApplication)); - - setSortOrder(baseApplication.getSortByDistance()); + val stationFilter = baseApplication.stationFilter + setChipStatus( + photoFilter, + stationFilter.photoIcon, + stationFilter.isPhotoFilterActive, + R.string.no_text + ) + setChipStatus( + nicknameFilter, + stationFilter.nicknameIcon, + stationFilter.isNicknameFilterActive, + stationFilter.getNicknameText(this.baseApplication) + ) + setChipStatus( + activeFilter, + stationFilter.activeIcon, + stationFilter.isActiveFilterActive, + stationFilter.activeText + ) + setChipStatus( + countrySelection, + R.drawable.ic_countries_active_24px, + true, + getCountryText(baseApplication) + ) + setSortOrder(baseApplication.sortByDistance) } - private static String getCountryText(final BaseApplication baseApplication) { - return String.join(",", baseApplication.getCountryCodes()); - } + private fun showActiveFilter(v: View) { + val popup = PopupMenu(activity, v) + popup.menuInflater.inflate(R.menu.active_filter, popup.menu) + popup.setOnMenuItemClickListener { menuItem: MenuItem -> + val stationFilter = baseApplication.stationFilter + when (menuItem.itemId) { + R.id.active_filter_active -> { + stationFilter.isActive = java.lang.Boolean.TRUE + } - private void showActiveFilter(View v) { - var popup = new PopupMenu(activity, v); - popup.getMenuInflater().inflate(R.menu.active_filter, popup.getMenu()); + R.id.active_filter_inactive -> { + stationFilter.isActive = java.lang.Boolean.FALSE + } - popup.setOnMenuItemClickListener(menuItem -> { - var stationFilter = baseApplication.getStationFilter(); - if (menuItem.getItemId() == R.id.active_filter_active) { - stationFilter.setActive(Boolean.TRUE); - } else if (menuItem.getItemId() == R.id.active_filter_inactive) { - stationFilter.setActive(Boolean.FALSE); - } else { - stationFilter.setActive(null); + else -> { + stationFilter.isActive = null + } } - setChipStatus(activeFilter, stationFilter.getActiveIcon(), stationFilter.isActiveFilterActive(), R.string.no_text); - updateStationFilter(stationFilter); - return false; - }); - - setPopupMenuIcons(popup); - popup.setOnDismissListener(menu -> setCloseIcon(activeFilter, R.drawable.ic_baseline_arrow_drop_up_24)); - popup.show(); - setCloseIcon(activeFilter, R.drawable.ic_baseline_arrow_drop_down_24); + setChipStatus( + activeFilter, + stationFilter.activeIcon, + stationFilter.isActiveFilterActive, + R.string.no_text + ) + updateStationFilter(stationFilter) + false + } + setPopupMenuIcons(popup) + popup.setOnDismissListener { + setCloseIcon( + activeFilter, + R.drawable.ic_baseline_arrow_drop_up_24 + ) + } + popup.show() + setCloseIcon(activeFilter, R.drawable.ic_baseline_arrow_drop_down_24) } - private void showPhotoFilter(View v) { - var popup = new PopupMenu(activity, v); - popup.getMenuInflater().inflate(R.menu.photo_filter, popup.getMenu()); + private fun showPhotoFilter(v: View) { + val popup = PopupMenu(activity, v) + popup.menuInflater.inflate(R.menu.photo_filter, popup.menu) + popup.setOnMenuItemClickListener { menuItem: MenuItem -> + val stationFilter = baseApplication.stationFilter + when (menuItem.itemId) { + R.id.photo_filter_has_photo -> { + stationFilter.setPhoto(java.lang.Boolean.TRUE) + } - popup.setOnMenuItemClickListener(menuItem -> { - var stationFilter = baseApplication.getStationFilter(); - if (menuItem.getItemId() == R.id.photo_filter_has_photo) { - stationFilter.setPhoto(Boolean.TRUE); - } else if (menuItem.getItemId() == R.id.photo_filter_without_photo) { - stationFilter.setPhoto(Boolean.FALSE); - } else { - stationFilter.setPhoto(null); - } - setChipStatus(photoFilter, stationFilter.getPhotoIcon(), stationFilter.isPhotoFilterActive(), R.string.no_text); - updateStationFilter(stationFilter); - return false; - }); + R.id.photo_filter_without_photo -> { + stationFilter.setPhoto(java.lang.Boolean.FALSE) + } - setPopupMenuIcons(popup); - popup.setOnDismissListener(menu -> setCloseIcon(photoFilter, R.drawable.ic_baseline_arrow_drop_up_24)); - popup.show(); - setCloseIcon(photoFilter, R.drawable.ic_baseline_arrow_drop_down_24); + else -> { + stationFilter.setPhoto(null) + } + } + setChipStatus( + photoFilter, + stationFilter.photoIcon, + stationFilter.isPhotoFilterActive, + R.string.no_text + ) + updateStationFilter(stationFilter) + false + } + setPopupMenuIcons(popup) + popup.setOnDismissListener { + setCloseIcon( + photoFilter, + R.drawable.ic_baseline_arrow_drop_up_24 + ) + } + popup.show() + setCloseIcon(photoFilter, R.drawable.ic_baseline_arrow_drop_down_24) } - public void selectCountry(View view) { - getContext().startActivity(new Intent(getContext(), CountryActivity.class)); + private fun selectCountry() { + context.startActivity(Intent(context, CountryActivity::class.java)) } - private void showSortMenu(View v) { - var popup = new PopupMenu(activity, v); - popup.getMenuInflater().inflate(R.menu.sort_order, popup.getMenu()); - - popup.setOnMenuItemClickListener(menuItem -> { - boolean sortByDistance = menuItem.getItemId() == R.id.sort_order_by_distance; - setSortOrder(sortByDistance); - baseApplication.setSortByDistance(sortByDistance); + private fun showSortMenu(v: View) { + val popup = PopupMenu(activity, v) + popup.menuInflater.inflate(R.menu.sort_order, popup.menu) + popup.setOnMenuItemClickListener { menuItem: MenuItem -> + val sortByDistance = menuItem.itemId == R.id.sort_order_by_distance + setSortOrder(sortByDistance) + baseApplication.sortByDistance = sortByDistance if (listener != null) { - listener.sortOrderChanged(sortByDistance); + listener!!.sortOrderChanged(sortByDistance) } - return false; - }); - - setPopupMenuIcons(popup); - popup.setOnDismissListener(menu -> setCloseIcon(toggleSort, R.drawable.ic_baseline_arrow_drop_up_24)); - popup.show(); - setCloseIcon(toggleSort, R.drawable.ic_baseline_arrow_drop_down_24); + false + } + setPopupMenuIcons(popup) + popup.setOnDismissListener { + setCloseIcon( + toggleSort, + R.drawable.ic_baseline_arrow_drop_up_24 + ) + } + popup.show() + setCloseIcon(toggleSort, R.drawable.ic_baseline_arrow_drop_down_24) } @SuppressLint("RestrictedApi") - private void setPopupMenuIcons(final PopupMenu popup) { + private fun setPopupMenuIcons(popup: PopupMenu) { try { - if (popup.getMenu() instanceof MenuBuilder menuBuilder) { - menuBuilder.setOptionalIconsVisible(true); - for (var item : menuBuilder.getVisibleItems()) { - var iconMarginPx = - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0, getResources().getDisplayMetrics()); - if (item.getIcon() != null) { - InsetDrawable icon; - icon = new InsetDrawable(item.getIcon(), iconMarginPx, 0, iconMarginPx, 0); - icon.setTint(getResources().getColor(R.color.colorSurface, null)); - item.setIcon(icon); - } + val menuBuilder = popup.menu + if (menuBuilder is MenuBuilder) { + menuBuilder.setOptionalIconsVisible(true) + for (item in menuBuilder.visibleItems) { + val iconMarginPx = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 0f, + resources.displayMetrics + ) + val icon = + InsetDrawable(item.icon, iconMarginPx, 0f, iconMarginPx, 0f) + icon.setTint(resources.getColor(R.color.colorSurface, null)) + item.icon = icon } } - } catch (Exception e) { - Log.w(TAG, "Error setting popupMenuIcons: ", e); + } catch (e: Exception) { + Log.w(TAG, "Error setting popupMenuIcons: ", e) } } - public void setSortOrder(boolean sortByDistance) { - setChipStatus(toggleSort, sortByDistance ? R.drawable.ic_sort_by_distance_active_24px : R.drawable.ic_sort_by_alpha_active_24px, true, R.string.no_text); + fun setSortOrder(sortByDistance: Boolean) { + setChipStatus( + toggleSort, + if (sortByDistance) R.drawable.ic_sort_by_distance_active_24px else R.drawable.ic_sort_by_alpha_active_24px, + true, + R.string.no_text + ) } - public void selectNicknameFilter(View view) { - var nicknames = baseApplication.getDbAdapter().getPhotographerNicknames(); - var stationFilter = baseApplication.getStationFilter(); - if (nicknames.length == 0) { - Toast.makeText(getContext(), getContext().getString(R.string.no_nicknames_found), Toast.LENGTH_LONG).show(); - return; + private fun selectNicknameFilter() { + val nicknames = baseApplication.dbAdapter.photographerNicknames + val stationFilter = baseApplication.stationFilter + if (nicknames.isEmpty()) { + Toast.makeText( + context, + context.getString(R.string.no_nicknames_found), + Toast.LENGTH_LONG + ).show() + return } - int selectedNickname = IntStream.range(0, nicknames.length) - .filter(i -> nicknames[i].equals(stationFilter.getNickname())) - .findFirst().orElse(-1); - - new AlertDialog.Builder(new ContextThemeWrapper(getContext(), R.style.AlertDialogCustom)) - .setIcon(R.mipmap.ic_launcher) - .setTitle(R.string.select_nickname) - .setSingleChoiceItems(nicknames, selectedNickname, null) - .setPositiveButton(R.string.button_ok_text, (dialog, whichButton) -> { - dialog.dismiss(); - int selectedPosition = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); - if (selectedPosition >= 0 && nicknames.length > selectedPosition) { - stationFilter.setNickname(nicknames[selectedPosition]); - setChipStatus(nicknameFilter, stationFilter.getNicknameIcon(), stationFilter.isNicknameFilterActive(), stationFilter.getNicknameText(baseApplication)); - updateStationFilter(stationFilter); - } - }) - .setNeutralButton(R.string.button_remove_text, (dialog, whichButton) -> { - dialog.dismiss(); - stationFilter.setNickname(null); - setChipStatus(nicknameFilter, stationFilter.getNicknameIcon(), stationFilter.isNicknameFilterActive(), stationFilter.getNicknameText(baseApplication)); - updateStationFilter(stationFilter); - }) - .setNegativeButton(R.string.button_myself_text, (dialog, whichButton) -> { - dialog.dismiss(); - stationFilter.setNickname(baseApplication.getNickname()); - setChipStatus(nicknameFilter, stationFilter.getNicknameIcon(), stationFilter.isNicknameFilterActive(), stationFilter.getNicknameText(baseApplication)); - updateStationFilter(stationFilter); - }) - .create().show(); + val selectedNickname = IntStream.range(0, nicknames.size) + .filter { i: Int -> nicknames[i] == stationFilter.nickname } + .findFirst().orElse(-1) + AlertDialog.Builder(ContextThemeWrapper(context, R.style.AlertDialogCustom)) + .setIcon(R.mipmap.ic_launcher) + .setTitle(R.string.select_nickname) + .setSingleChoiceItems(nicknames, selectedNickname, null) + .setPositiveButton(R.string.button_ok_text) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + val selectedPosition = (dialog as AlertDialog).listView.checkedItemPosition + if (selectedPosition >= 0 && nicknames.size > selectedPosition) { + stationFilter.nickname = nicknames[selectedPosition] + setChipStatus( + nicknameFilter, + stationFilter.nicknameIcon, + stationFilter.isNicknameFilterActive, + stationFilter.getNicknameText(baseApplication) + ) + updateStationFilter(stationFilter) + } + } + .setNeutralButton(R.string.button_remove_text) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + stationFilter.nickname = null + setChipStatus( + nicknameFilter, + stationFilter.nicknameIcon, + stationFilter.isNicknameFilterActive, + stationFilter.getNicknameText(baseApplication) + ) + updateStationFilter(stationFilter) + } + .setNegativeButton(R.string.button_myself_text) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + stationFilter.nickname = baseApplication.nickname + setChipStatus( + nicknameFilter, + stationFilter.nicknameIcon, + stationFilter.isNicknameFilterActive, + stationFilter.getNicknameText(baseApplication) + ) + updateStationFilter(stationFilter) + } + .create().show() } - private void updateStationFilter(StationFilter stationFilter) { - baseApplication.setStationFilter(stationFilter); + private fun updateStationFilter(stationFilter: StationFilter) { + baseApplication.stationFilter = stationFilter if (listener != null) { - listener.stationFilterChanged(stationFilter); + listener!!.stationFilterChanged(stationFilter) } } - public void setSortOrderEnabled(boolean enabled) { - toggleSort.setVisibility(enabled ? VISIBLE : GONE); + fun setSortOrderEnabled(enabled: Boolean) { + toggleSort.visibility = if (enabled) VISIBLE else GONE } - public interface OnChangeListener { - void stationFilterChanged(StationFilter stationFilter); - - void sortOrderChanged(boolean sortByDistance); + interface OnChangeListener { + fun stationFilterChanged(stationFilter: StationFilter) + fun sortOrderChanged(sortByDistance: Boolean) } -} + companion object { + private val TAG = StationFilterBar::class.java.simpleName + private fun getTintedDrawable(unwrappedDrawable: Drawable?, color: Int): Drawable? { + if (unwrappedDrawable != null) { + val wrappedDrawable = DrawableCompat.wrap(unwrappedDrawable) + DrawableCompat.setTint(wrappedDrawable, color) + return wrappedDrawable + } + return null + } + + private fun getCountryText(baseApplication: BaseApplication): String { + return baseApplication.countryCodes.joinToString(",") + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/Cluster.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/Cluster.kt index 0e593d99..3320e808 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/Cluster.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/Cluster.kt @@ -1,160 +1,130 @@ /* - * Copyright 2009 Huan Erdao - * Copyright 2014 Martin Vennekamp - * Copyright 2015 mapsforge.org - * - * This program is free software: you can redistribute it and/or modify it under the - * terms of the GNU Lesser General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with - * this program. If not, see . + * Derived from https://github.com/mapsforge/mapsforge/blob/master/mapsforge-samples-android/src/main/java/org/mapsforge/samples/android/cluster/Cluster.java */ -package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge; +package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge -import org.mapsforge.core.model.LatLong; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import org.mapsforge.core.model.LatLong +import java.util.Collections /** * Cluster class. * contains single marker object(ClusterMarker). mostly wraps methods in ClusterMarker. */ -public class Cluster { - /** - * GeoClusterer object - */ - private ClusterManager clusterManager; +class Cluster( + var clusterManager: ClusterManager?, item: T +) { /** * Center of cluster */ - private LatLong center; + var location: LatLong? = null + private set + /** * List of GeoItem within cluster */ - private final List items = Collections.synchronizedList(new ArrayList<>()); - /** - * ClusterMarker object - */ - private ClusterMarker clusterMarker; + val items = Collections.synchronizedList(ArrayList()) - /** - * @param clusterManager ClusterManager object. - */ - public Cluster(ClusterManager clusterManager, T item) { - this.clusterManager = clusterManager; - this.clusterMarker = new ClusterMarker<>(this); - addItem(item); - } + private var clusterMarker: ClusterMarker? - public ClusterManager getClusterManager() { - return clusterManager; + init { + clusterMarker = ClusterMarker(this) + addItem(item) } - public String getTitle() { - if (getItems().size() == 1) { - return getItems().get(0).getTitle(); - } - synchronized (items) { - return items.stream().filter(GeoItem::hasPhoto).count() + "/" + getItems().size(); + val title: String + get() { + if (getItems().size == 1) { + return getItems()[0].title ?: "" + } + synchronized(items) { + return items.stream().filter { obj: T -> obj.hasPhoto() }.count() + .toString() + "/" + getItems().size + } } - } /** * add item to cluster object * * @param item GeoItem object to be added. */ - public synchronized void addItem(T item) { - synchronized (items) { - items.add(item); - } - if (center == null) { - center = item.getLatLong(); + @Synchronized + fun addItem(item: T) { + synchronized(items) { items.add(item) } + if (location == null) { + location = item.latLong } else { // computing the centroid - double lat = 0, lon = 0; - int n = 0; - synchronized (items) { - for (T object : items) { - if (object == null) { - throw new NullPointerException("object == null"); - } - if (object.getLatLong() == null) { - throw new NullPointerException("object.getLatLong() == null"); - } - lat += object.getLatLong().latitude; - lon += object.getLatLong().longitude; - n++; + var lat = 0.0 + var lon = 0.0 + var n = 0 + synchronized(items) { + items.forEach { + lat += it.latLong.latitude + lon += it.latLong.longitude + n++ } } - center = new LatLong(lat / n, lon / n); + location = LatLong(lat / n, lon / n) } } - /** - * get center of the cluster. - * - * @return center of the cluster in LatLong. - */ - public LatLong getLocation() { - return center; - } - /** * get list of GeoItem. * * @return list of GeoItem within cluster. */ - public synchronized List getItems() { - return items; + @Synchronized + fun getItems(): List { + return items } /** * clears cluster object and removes the cluster from the layers collection. */ - public void clear() { + fun clear() { if (clusterMarker != null) { - var mapOverlays = clusterManager.getMapView().getLayerManager().getLayers(); - if (mapOverlays.contains(clusterMarker)) { - mapOverlays.remove(clusterMarker); + val mapOverlays = clusterManager?.mapView?.layerManager?.layers + if (mapOverlays != null) { + if (mapOverlays.contains(clusterMarker)) { + mapOverlays.remove(clusterMarker) + } } - clusterManager = null; - clusterMarker = null; - } - synchronized (items) { - items.clear(); + clusterManager = null + clusterMarker = null } + synchronized(items) { items.clear() } } /** * add the ClusterMarker to the Layers if is within Viewport, otherwise remove. */ - public void redraw() { - var mapOverlays = clusterManager.getMapView().getLayerManager().getLayers(); - if (clusterMarker != null && center != null - && clusterManager.getCurBounds() != null - && !clusterManager.getCurBounds().contains(center) - && mapOverlays.contains(clusterMarker)) { - mapOverlays.remove(clusterMarker); - return; + fun redraw() { + if (clusterManager == null || clusterMarker == null) { + return } - if (clusterMarker != null - && mapOverlays.size() > 0 - && !mapOverlays.contains(clusterMarker) - && !clusterManager.isClustering) { - mapOverlays.add(1, clusterMarker); + val mapOverlays = clusterManager?.mapView?.layerManager?.layers + if (mapOverlays != null) { + if (clusterMarker != null && location != null && clusterManager?.curBounds != null && !clusterManager!!.curBounds + ?.contains( + location + )!! + && mapOverlays.contains(clusterMarker) + ) { + mapOverlays.remove(clusterMarker) + return + } + } + if (mapOverlays != null) { + if (clusterMarker != null && mapOverlays.size() > 0 && !mapOverlays.contains( + clusterMarker + ) + && !clusterManager!!.isClustering + ) { + mapOverlays.add(1, clusterMarker) + } } } - public int getSize() { - return items.size(); - } - -} + val size: Int + get() = items.size +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/ClusterManager.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/ClusterManager.kt index fb0ce88c..a8546bdb 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/ClusterManager.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/ClusterManager.kt @@ -1,151 +1,102 @@ /* - * Copyright 2009 Huan Erdao - * Copyright 2014 Martin Vennekamp - * Copyright 2015 mapsforge.org - * - * This program is free software: you can redistribute it and/or modify it under the - * terms of the GNU Lesser General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with - * this program. If not, see . + * Derived from https://github.com/mapsforge/mapsforge/blob/master/mapsforge-samples-android/src/main/java/org/mapsforge/samples/android/cluster/ClusterManager.java */ -package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge; - -import android.util.Log; -import android.widget.Toast; - -import org.mapsforge.core.model.BoundingBox; -import org.mapsforge.core.model.LatLong; -import org.mapsforge.map.model.DisplayModel; -import org.mapsforge.map.model.common.Observer; -import org.mapsforge.map.view.MapView; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge + +import android.util.Log +import android.widget.Toast +import org.mapsforge.core.model.BoundingBox +import org.mapsforge.core.model.LatLong +import org.mapsforge.map.model.DisplayModel +import org.mapsforge.map.model.common.Observer +import org.mapsforge.map.view.MapView +import java.util.Collections +import java.util.function.Consumer /** - * Class for Clustering geotagged content. this clustering came from - * "markerclusterer" which is available as opensource at - * https://code.google.com/p/android-maps-extensions/, resp - * https://github.com/googlemaps/android-maps-utils this is android ported - * version with modification to fit to the application refer also to other - * implementations: - * https://code.google.com/p/osmbonuspack/source/browse/#svn%2Ftrunk - * %2FOSMBonusPack%2Fsrc%2Forg%2Fosmdroid%2Fbonuspack%2Fclustering - * http://developer.nokia.com/community/wiki/ - * Map_Marker_Clustering_Strategies_for_the_Maps_API_for_Java_ME - *

- * based on http://ge.tt/7Zq63CH/v/1 - *

- * Should be added as Observer on Mapsforge frameBufferModel. + * Class for Clustering geotagged content */ -public class ClusterManager implements Observer, TapHandler { - private static final String TAG = ClusterManager.class.getSimpleName(); - protected static final int MIN_CLUSTER_SIZE = 5; - /** - * A 'Toast' to display information, intended to show information on {@link ClusterMarker} - * with more than one {@link GeoItem} (while Marker with a single GeoItem should have their - * own OnClick functions) - */ - protected static Toast toast; +class ClusterManager( + val mapView: MapView, + markerBitmaps: List, maxClusteringZoom: Byte, tapHandler: TapHandler? +) : Observer, TapHandler { + /** * grid size for Clustering(dip). */ - protected final float GRIDSIZE = 60 * DisplayModel.getDeviceScaleFactor(); - /** - * MapView object. - */ - protected final MapView mapView; + private val gridsize = 60 * DisplayModel.getDeviceScaleFactor() /** * The maximum (highest) zoom level for clustering the items., */ - protected final byte maxClusteringZoom; + private val maxClusteringZoom: Byte + /** * Lock for the re-Clustering of the items. */ - public boolean isClustering = false; + var isClustering = false + /** * MarkerBitmap object for marker icons, uses Static access. */ - protected final List markerIconBmps; + val markerIconBmps: List /** * The current BoundingBox of the viewport. */ - protected BoundingBox currBoundingBox; + private var currBoundingBox: BoundingBox? = null + /** * GeoItem ArrayList object that are out of viewport and need to be * clustered on panning. */ - protected final List leftItems = Collections.synchronizedList(new ArrayList<>()); + val leftItems: MutableList = Collections.synchronizedList(ArrayList()) + /** * Clustered object list. */ - protected final List> clusters = Collections.synchronizedList(new ArrayList<>()); + val clusters: MutableList> = Collections.synchronizedList(ArrayList>()) + /** * Handles on click on markers */ - protected final TapHandler tapHandler; + private val tapHandler: TapHandler? /** - * saves the actual ZoolLevel of the MapView - */ - private double oldZoolLevel; - /** - * saves the actual Center as LatLong of the MapViewPosition + * saves the actual ZoomLevel of the MapView */ - private LatLong oldCenterLatLong; - private ClusterTask clusterTask; + private var oldZoomLevel: Double /** - * @param mapView The Mapview in which the markers are shoen - * @param markerBitmaps a list of well formed {@link MarkerBitmap} - * @param maxClusteringZoom The maximum zoom level, beyond this level no clustering is performed. + * saves the actual Center as LatLong of the MapViewPosition */ - public ClusterManager(MapView mapView, - List markerBitmaps, byte maxClusteringZoom, TapHandler tapHandler) { - this.mapView = mapView; + private var oldCenterLatLong: LatLong + private var clusterTask: ClusterTask? = null + init { // set to impossible values to trigger clustering at first onChange - oldZoolLevel = -1; - oldCenterLatLong = new LatLong(-90.0, -180.0); - this.markerIconBmps = markerBitmaps; - - this.maxClusteringZoom = maxClusteringZoom; - this.tapHandler = tapHandler; - } - - /** - * You might like to set the Toast from external, in order to make sure that only a single Toast - * is showing up. - * - * @param toast A 'Toast' to display information, intended to show information on {@link ClusterMarker} - */ - public static void setToast(Toast toast) { - ClusterManager.toast = toast; + oldZoomLevel = -1.0 + oldCenterLatLong = LatLong(-90.0, -180.0) + markerIconBmps = markerBitmaps + this.maxClusteringZoom = maxClusteringZoom + this.tapHandler = tapHandler } - public MapView getMapView() { - return mapView; - } - - public synchronized List getAllItems() { - List rtnList = Collections.synchronizedList(new ArrayList<>()); - synchronized (leftItems) { - rtnList.addAll(leftItems); - } - synchronized (clusters) { - clusters.stream().map(Cluster::getItems).forEach(rtnList::addAll); + @get:Synchronized + val allItems: List + get() { + val rtnList = Collections.synchronizedList(ArrayList()) + synchronized(leftItems) { rtnList.addAll(leftItems) } + synchronized(clusters) { + clusters.map { obj: Cluster -> obj.items } + .forEach { c: List? -> + rtnList.addAll( + c!! + ) + } + } + return rtnList } - return rtnList; - } /** * add item and do isClustering. NOTE: this method will not redraw screen. @@ -153,51 +104,46 @@ public class ClusterManager implements Observer, TapHandler= mapView.getModel().mapViewPosition - .getZoomLevel()) { + } else if (maxClusteringZoom >= mapView.model.mapViewPosition + .zoomLevel + ) { // else add to a cluster; - var pos = mapView.getMapViewProjection().toPixels(item.getLatLong()); + val pos = mapView.mapViewProjection.toPixels(item.latLong) // check existing cluster - synchronized (clusters) { - for (Cluster mCluster : clusters) { - if (clusterTask != null && clusterTask.isInterrupted()) { - return; - } - if (mCluster.getItems().size() == 0) { - throw new IllegalArgumentException("cluster.getItems().size() == 0"); + synchronized(clusters) { + for (mCluster in clusters) { + if (clusterTask != null && clusterTask!!.isInterrupted) { + return } + require(mCluster.items.size != 0) { "cluster.getItems().size() == 0" } // find a cluster which contains the marker. // use 1st element to fix the location, hinder the cluster from // running around and isClustering. - var gpCenter = mCluster.getItems().get(0).getLatLong(); - if (gpCenter == null) { - throw new IllegalArgumentException(); - } - - var ptCenter = mapView.getMapViewProjection().toPixels(gpCenter); + val gpCenter = + mCluster.items[0].latLong + val ptCenter = mapView.mapViewProjection.toPixels(gpCenter) // find a cluster which contains the marker. - if (pos.distance(ptCenter) <= GRIDSIZE) { - mCluster.addItem(item); - return; + if (pos.distance(ptCenter) <= gridsize) { + mCluster.addItem(item) + return } } // No cluster contain the marker, create a new cluster. - clusters.add(createCluster(item)); + clusters.add(createCluster(item)) } } else { // No clustering allowed, create a new cluster with single item. - synchronized (clusters) { - clusters.add(createCluster(item)); - } + synchronized(clusters) { clusters.add(createCluster(item)) } } } @@ -207,30 +153,30 @@ public class ClusterManager implements Observer, TapHandler createCluster(T item) { - return new Cluster<>(this, item); + private fun createCluster(item: T): Cluster { + return Cluster(this, item) } /** * redraws clusters */ - public synchronized void redraw() { - synchronized (clusters) { + @Synchronized + fun redraw() { + synchronized(clusters) { if (!isClustering) { - List> removed = new ArrayList<>(); - List> singles = new ArrayList<>(); - clusters.stream() - .filter(mCluster -> mCluster.getSize() < MIN_CLUSTER_SIZE) - .forEach(mCluster -> { - mCluster.getItems().forEach(item -> singles.add(createCluster(item))); - mCluster.clear(); - removed.add(mCluster); - }); - clusters.removeAll(removed); - clusters.addAll(singles); - - for (Cluster mCluster : clusters) { - mCluster.redraw(); + val removed = mutableListOf>() + val singles = mutableListOf>() + clusters + .filter { mCluster -> mCluster.size < MIN_CLUSTER_SIZE } + .forEach { mCluster -> + mCluster.items.forEach(Consumer { item -> singles.add(createCluster(item)) }) + mCluster.clear() + removed.add(mCluster) + } + clusters.removeAll(removed) + clusters.addAll(singles) + for (mCluster in clusters) { + mCluster.redraw() } } } @@ -241,84 +187,85 @@ public class ClusterManager implements Observer, TapHandler nw_.latitude) { - currBoundingBox = new BoundingBox(nw_.latitude, se_.longitude, se_.latitude, - nw_.longitude); - } else { - currBoundingBox = new BoundingBox(se_.latitude, nw_.longitude, nw_.latitude, - se_.longitude); + @get:Synchronized + val curBounds: BoundingBox? + /** + * get the current BoundingBox of the viewport + * + * @return current BoundingBox of the viewport + */ + get() { + if (currBoundingBox == null) { + require(!(mapView.width <= 0 || mapView.height <= 0)) { + (" mapView.getWidth() <= 0 " + + "|| mapView.getHeight() <= 0 " + + mapView.width + " || " + mapView.height) + } + /* North-West geo point of the bound */ + val nw = mapView.mapViewProjection.fromPixels( + -mapView.width * 0.5, + -mapView.height * 0.5 + ) + /* South-East geo point of the bound */ + val se = mapView.mapViewProjection.fromPixels( + mapView.width + mapView.width * 0.5, + mapView.height + mapView.height * 0.5 + ) + if (se != null && nw != null) { + currBoundingBox = if (se.latitude > nw.latitude) { + BoundingBox( + nw.latitude, se.longitude, se.latitude, + nw.longitude + ) + } else { + BoundingBox( + se.latitude, nw.longitude, nw.latitude, + se.longitude + ) + } } } + return currBoundingBox } - return currBoundingBox; - } /** * add items that were not clustered in last isClustering. */ - private void addLeftItems() { - if (leftItems.size() == 0) { - return; - } - ArrayList currentLeftItems = new ArrayList<>(leftItems); - - synchronized (leftItems) { - leftItems.clear(); + private fun addLeftItems() { + if (leftItems.size == 0) { + return } - currentLeftItems.forEach(this::addItem); + val currentLeftItems = ArrayList(leftItems) + synchronized(leftItems) { leftItems.clear() } + currentLeftItems.forEach(Consumer { item: T -> addItem(item) }) } // ********************************************************************************************************************* // Methods to implement 'Observer' // ********************************************************************************************************************* - - @Override - public synchronized void onChange() { - currBoundingBox = null; + @Synchronized + override fun onChange() { + currBoundingBox = null if (isClustering) { - return; + return } - - if (oldZoolLevel != mapView.getModel().mapViewPosition.getZoomLevel()) { + if (oldZoomLevel != mapView.model.mapViewPosition.zoomLevel.toDouble()) { // react on zoom changes - oldZoolLevel = mapView.getModel().mapViewPosition.getZoomLevel(); - resetViewport(false); + oldZoomLevel = mapView.model.mapViewPosition.zoomLevel.toDouble() + resetViewport(false) } else { // react on position changes - var mapViewPosition = mapView.getModel().mapViewPosition; - - var posOld = mapView.getMapViewProjection().toPixels(oldCenterLatLong); - var posNew = mapView.getMapViewProjection().toPixels(mapViewPosition.getCenter()); - if (posOld != null && posOld.distance(posNew) > GRIDSIZE / 2) { - // Log.d(TAG, "moving..."); - oldCenterLatLong = mapViewPosition.getCenter(); - resetViewport(true); + val mapViewPosition = mapView.model.mapViewPosition + val posOld = mapView.mapViewProjection.toPixels(oldCenterLatLong) + val posNew = mapView.mapViewProjection.toPixels(mapViewPosition.center) + if (posOld != null && posOld.distance(posNew) > gridsize / 2) { + oldCenterLatLong = mapViewPosition.center + resetViewport(true) } } } @@ -327,82 +274,81 @@ public class ClusterManager implements Observer, TapHandler { - cluster.getClusterManager().cancelClusterTask(); - cluster.clear(); - }); - clusters.clear(); - } - markerIconBmps.forEach(MarkerBitmap::decrementRefCounters); - synchronized (leftItems) { - leftItems.clear(); + @Synchronized + fun destroyGeoClusterer() { + synchronized(clusters) { + clusters.forEach(Consumer { cluster: Cluster -> + cluster.clusterManager?.cancelClusterTask() + cluster.clear() + }) + clusters.clear() } - MarkerBitmap.clearCaptionBitmap(); + markerIconBmps.forEach(Consumer { markerBitmap -> markerBitmap!!.decrementRefCounters() }) + synchronized(leftItems) { leftItems.clear() } + MarkerBitmap.clearCaptionBitmap() } - @Override - public void onTap(T item) { - if (tapHandler != null) { - tapHandler.onTap(item); - } + override fun onTap(item: T) { + tapHandler?.onTap(item) } - private class ClusterTask extends Thread { - - private final boolean isMoving; - - public ClusterTask(boolean isMoving) { - this.isMoving = isMoving; - } - - @Override - public void run() { - Log.d(TAG, "Run ClusterTask"); + private inner class ClusterTask(private val isMoving: Boolean) : Thread() { + override fun run() { + Log.d(TAG, "Run ClusterTask") // If the map is moved without zoom-change: Add unclustered items. if (isMoving) { - addLeftItems(); - } - // If the cluster zoom level changed then destroy the cluster and - // collect its markers. - else { - synchronized (clusters) { - for (Cluster mCluster : clusters) { - synchronized (leftItems) { - leftItems.addAll(mCluster.getItems()); - } - mCluster.clear(); + addLeftItems() + } else { + synchronized(clusters) { + for (mCluster in clusters) { + synchronized(leftItems) { leftItems.addAll(mCluster.items) } + mCluster.clear() } } - synchronized (clusters) { - clusters.clear(); - } - if (!isInterrupted()) { - synchronized (clusters) { - if (clusters.size() != 0) { - throw new IllegalArgumentException(); - } - } - addLeftItems(); + synchronized(clusters) { clusters.clear() } + if (!isInterrupted) { + synchronized(clusters) { require(clusters.size == 0) } + addLeftItems() } } - isClustering = false; - redraw(); - Log.d(TAG, "ClusterTask finished"); + isClustering = false + redraw() + Log.d(TAG, "ClusterTask finished") } } -} + companion object { + private val TAG = ClusterManager::class.java.simpleName + private const val MIN_CLUSTER_SIZE = 5 + + /** + * A 'Toast' to display information, intended to show information on [ClusterMarker] + * with more than one [GeoItem] (while Marker with a single GeoItem should have their + * own OnClick functions) + */ + var toast: Toast? = null + + /** + * You might like to set the Toast from external, in order to make sure that only a single Toast + * is showing up. + * + * @param toast A 'Toast' to display information, intended to show information on [ClusterMarker] + */ + fun setToast(toast: Toast?) { + Companion.toast = toast + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/ClusterMarker.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/ClusterMarker.kt index 0a0bd550..c4575b75 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/ClusterMarker.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/ClusterMarker.kt @@ -1,203 +1,199 @@ /* - * Copyright 2009 Huan Erdao - * Copyright 2014 Martin Vennekamp - * Copyright 2015 mapsforge.org - * - * This program is free software: you can redistribute it and/or modify it under the - * terms of the GNU Lesser General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with - * this program. If not, see . + * Derived from https://github.com/mapsforge/mapsforge/blob/master/mapsforge-samples-android/src/main/java/org/mapsforge/samples/android/cluster/ClusterMarker.java */ -package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge; +package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge -import static java.util.stream.Collectors.joining; - -import android.util.Log; - -import org.mapsforge.core.model.BoundingBox; -import org.mapsforge.core.model.LatLong; -import org.mapsforge.core.model.Point; -import org.mapsforge.core.model.Rectangle; -import org.mapsforge.core.util.MercatorProjection; -import org.mapsforge.map.layer.Layer; +import android.util.Log +import org.mapsforge.core.graphics.Canvas +import org.mapsforge.core.model.BoundingBox +import org.mapsforge.core.model.LatLong +import org.mapsforge.core.model.Point +import org.mapsforge.core.model.Rectangle +import org.mapsforge.core.util.MercatorProjection +import org.mapsforge.map.layer.Layer /** * Layer extended class to display Clustered Marker. * * @param */ -public class ClusterMarker extends Layer { - private static final String TAG = "ClusterMarker"; - - /** - * cluster object - */ - protected final Cluster cluster; +class ClusterMarker(private val cluster: Cluster) : Layer() { /** * icon marker type */ - protected int markerType = 0; - - /** - * @param cluster a cluster to be rendered for this marker - */ - public ClusterMarker(Cluster cluster) { - this.cluster = cluster; - } + private var markerType = 0 /** * change icon bitmaps according to the state and content size. */ - private void setMarkerBitmap() { - for (markerType = 0; markerType < cluster.getClusterManager().markerIconBmps.size(); markerType++) { + private fun setMarkerBitmap() { + markerType = 0 + while (markerType < (cluster.clusterManager?.markerIconBmps?.size ?: 0)) { + // Check if the number of items in this cluster is below or equal the limit of the MarkerBitMap - if (cluster.getItems().size() <= cluster.getClusterManager() - .markerIconBmps.get(markerType).getItemMax()) { - return; + if (cluster.items.size <= (cluster.clusterManager?.markerIconBmps?.get(markerType)?.itemMax + ?: 0) + ) { + return } + markerType++ } // set the markerType to maximum value ==> reduce markerType by one. - markerType--; + markerType-- } - @Override - public synchronized void draw(BoundingBox boundingBox, byte zoomLevel, - org.mapsforge.core.graphics.Canvas canvas, Point topLeftPoint) { - if (cluster.getClusterManager() == null || - this.getLatLong() == null) { - return; + @Synchronized + override fun draw( + boundingBox: BoundingBox, zoomLevel: Byte, + canvas: Canvas, topLeftPoint: Point + ) { + if (cluster.clusterManager == null || + latLong == null + ) { + return } - setMarkerBitmap(); - var mapSize = MercatorProjection.getMapSize(zoomLevel, this.displayModel.getTileSize()); - var pixelX = MercatorProjection.longitudeToPixelX(this.getLatLong().longitude, mapSize); - var pixelY = MercatorProjection.latitudeToPixelY(this.getLatLong().latitude, mapSize); - double halfBitmapWidth; - double halfBitmapHeight; - var markerBitmap = cluster.getClusterManager().markerIconBmps.get(markerType); - var bitmap = markerBitmap.getBitmap(hasPhoto(), ownPhoto(), stationActive(), isPendingUpload()); + setMarkerBitmap() + val mapSize = MercatorProjection.getMapSize(zoomLevel, displayModel.tileSize) + val pixelX = MercatorProjection.longitudeToPixelX(latLong!!.longitude, mapSize) + val pixelY = MercatorProjection.latitudeToPixelY(latLong!!.latitude, mapSize) + val halfBitmapWidth: Double + val halfBitmapHeight: Double + val markerBitmap = cluster.clusterManager!!.markerIconBmps[markerType] + val bitmap = + markerBitmap.getBitmap(hasPhoto(), ownPhoto(), stationActive(), isPendingUpload) try { - halfBitmapWidth = bitmap.getWidth() / 2f; - halfBitmapHeight = bitmap.getHeight() / 2f; - } catch (NullPointerException e) { - Log.e(ClusterMarker.TAG, e.getMessage(), e); - return; + halfBitmapWidth = (bitmap.width / 2f).toDouble() + halfBitmapHeight = (bitmap.height / 2f).toDouble() + } catch (e: NullPointerException) { + Log.e(TAG, e.message, e) + return } - int left = (int) (pixelX - topLeftPoint.x - halfBitmapWidth + markerBitmap.getIconOffset().x); - int top = (int) (pixelY - topLeftPoint.y - halfBitmapHeight + markerBitmap.getIconOffset().y); - int right = (left + bitmap.getWidth()); - int bottom = (top + bitmap.getHeight()); - var bitmapRectangle = new Rectangle(left, top, right, bottom); - var canvasRectangle = new Rectangle(0, 0, canvas.getWidth(), canvas.getHeight()); + val left = (pixelX - topLeftPoint.x - halfBitmapWidth + markerBitmap.iconOffset.x).toInt() + val top = (pixelY - topLeftPoint.y - halfBitmapHeight + markerBitmap.iconOffset.y).toInt() + val right = left + bitmap.width + val bottom = top + bitmap.height + val bitmapRectangle = + Rectangle(left.toDouble(), top.toDouble(), right.toDouble(), bottom.toDouble()) + val canvasRectangle = Rectangle(0.0, 0.0, canvas.width.toDouble(), canvas.height.toDouble()) if (!canvasRectangle.intersects(bitmapRectangle)) { - return; + return } // Draw bitmap - canvas.drawBitmap(bitmap, left, top); + canvas.drawBitmap(bitmap, left, top) // Draw Text if (zoomLevel > 13) { if (markerType == 0) { // Draw bitmap - var bubble = MarkerBitmap.getBitmapFromTitle(cluster.getTitle(), - markerBitmap.getPaint()); - canvas.drawBitmap(bubble, - (int) (left + halfBitmapWidth - bubble.getWidth() / 2), - (top - bubble.getHeight())); + MarkerBitmap.getBitmapFromTitle( + cluster.title, + markerBitmap.paint + )?.let { + canvas.drawBitmap( + it, (left + halfBitmapWidth - it.width / 2).toInt(), + top - it.height + ) + } } else { - int x = (int) (left + bitmap.getWidth() * 1.3); - int y = (int) (top + bitmap.getHeight() * 1.3 - + markerBitmap.getPaint().getTextHeight(cluster.getTitle()) / 2); - canvas.drawText(cluster.getTitle(), x, y, - markerBitmap.getPaint()); + val x = (left + bitmap.width * 1.3).toInt() + val y = + (top + bitmap.height * 1.3 + markerBitmap.paint.getTextHeight(cluster.title) / 2).toInt() + canvas.drawText( + cluster.title, x, y, + markerBitmap.paint + ) } } - } - /** - * get center location of the marker. - * - * @return GeoPoint object of current marker center. - */ - public LatLong getLatLong() { - return cluster.getLocation(); - } + private val latLong: LatLong? + /** + * get center location of the marker. + * + * @return GeoPoint object of current marker center. + */ + get() = cluster.location /** * @return Gets the LatLong Position of the Layer Object */ - @Override - public LatLong getPosition() { - return getLatLong(); + override fun getPosition(): LatLong { + return latLong!! } - @Override - public synchronized boolean onTap(LatLong geoPoint, Point viewPosition, - Point tapPoint) { - if (cluster.getItems().size() == 1 && contains(viewPosition, tapPoint)) { - Log.w(ClusterMarker.TAG, "The Marker was touched with onTap: " - + this.getPosition().toString()); - cluster.getClusterManager().onTap(cluster.getItems().get(0)); - requestRedraw(); - return true; + @Synchronized + override fun onTap( + geoPoint: LatLong, viewPosition: Point, + tapPoint: Point + ): Boolean { + if (cluster.items.size == 1 && contains(viewPosition, tapPoint)) { + Log.w( + TAG, "The Marker was touched with onTap: " + + this.position.toString() + ) + cluster.clusterManager?.onTap(cluster.items[0]) + requestRedraw() + return true } else if (contains(viewPosition, tapPoint)) { - var builder = new StringBuilder(cluster.getItems().size() + " items:") - .append(cluster.getItems().stream() - .map(i -> "\n- " + i.getTitle()) - .limit(6) - .collect(joining())); - - if (cluster.getItems().size() > 6) { - builder.append("\n..."); + val builder = StringBuilder(cluster.items.size.toString() + " items:") + .append(cluster.items + .map { i -> + i.title + } + .take(6) + .joinToString("\n- ", "\n- ")) + if (cluster.items.size > 6) { + builder.append("\n...") } - - if (ClusterManager.toast != null) { - ClusterManager.toast.setText(builder); - ClusterManager.toast.show(); + ClusterManager.toast?.let { + it.setText(builder) + it.show() } } - return false; + return false } - public synchronized boolean contains(Point viewPosition, Point tapPoint) { - return getBitmapRectangle(viewPosition).contains(tapPoint); + @Synchronized + fun contains(viewPosition: Point, tapPoint: Point?): Boolean { + return getBitmapRectangle(viewPosition)?.contains(tapPoint) ?: false } - private Rectangle getBitmapRectangle(Point center) { - var markerBitmap = cluster.getClusterManager().markerIconBmps.get(markerType); - var bitmap = markerBitmap.getBitmap(hasPhoto(), ownPhoto(), stationActive(), isPendingUpload()); - return new Rectangle( - center.x - (float) bitmap.getWidth() + markerBitmap.getIconOffset().x, - center.y - (float) bitmap.getHeight() + markerBitmap.getIconOffset().y, - center.x + (float) bitmap.getWidth() + markerBitmap.getIconOffset().x, - center.y + (float) bitmap.getHeight() + markerBitmap.getIconOffset().y); + private fun getBitmapRectangle(center: Point): Rectangle? { + val markerBitmap = cluster.clusterManager?.markerIconBmps?.get(markerType) + val bitmap = + markerBitmap?.getBitmap(hasPhoto(), ownPhoto(), stationActive(), isPendingUpload) + if (markerBitmap != null && bitmap != null) { + return Rectangle( + center.x - bitmap.width.toFloat() + markerBitmap.iconOffset.x, + center.y - bitmap.height.toFloat() + markerBitmap.iconOffset.y, + center.x + bitmap.width.toFloat() + markerBitmap.iconOffset.x, + center.y + bitmap.height.toFloat() + markerBitmap.iconOffset.y + ) + } + return null } - public Boolean hasPhoto() { - return (cluster.getItems().size() == 1 && - cluster.getItems().get(0).hasPhoto()); + fun hasPhoto(): Boolean { + return cluster.items.size == 1 && + cluster.items[0]!!.hasPhoto() } - public Boolean ownPhoto() { - return (cluster.getItems().size() == 1 && - cluster.getItems().get(0).ownPhoto()); + private fun ownPhoto(): Boolean { + return cluster.items.size == 1 && + cluster.items[0]!!.ownPhoto() } - public Boolean stationActive() { - return (cluster.getItems().size() == 1 && - cluster.getItems().get(0).stationActive()); + private fun stationActive(): Boolean { + return cluster.items.size == 1 && + cluster.items[0]!!.stationActive() } - private boolean isPendingUpload() { - return (cluster.getItems().size() == 1 && - cluster.getItems().get(0).isPendingUpload()); - } + private val isPendingUpload: Boolean + get() = cluster.items.size == 1 && + cluster.items[0]!!.isPendingUpload -} + companion object { + private const val TAG = "ClusterMarker" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/DbsTileSource.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/DbsTileSource.kt index 973654a4..5c1e635b 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/DbsTileSource.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/DbsTileSource.kt @@ -1,19 +1,16 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge; +package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge -import org.mapsforge.map.layer.download.tilesource.OnlineTileSource; +import org.mapsforge.map.layer.download.tilesource.OnlineTileSource -public class DbsTileSource extends OnlineTileSource { - - public DbsTileSource(String name, String baseUrl) { - super(new String[]{"osm-prod.noncd.db.de"}, 8100); - - setName(name); - setBaseUrl(baseUrl); +class DbsTileSource(name: String?, baseUrl: String?) : + OnlineTileSource(arrayOf("osm-prod.noncd.db.de"), 8100) { + init { + setName(name) + setBaseUrl(baseUrl) setName(name).setAlpha(false) - .setBaseUrl(baseUrl) - .setParallelRequestsLimit(8).setProtocol("https").setTileSize(256) - .setZoomLevelMax((byte) 18).setZoomLevelMin((byte) 0); - setApiKey("P9roW0ePGY9TCRiXh8y5P1T4traxBrWl"); + .setBaseUrl(baseUrl) + .setParallelRequestsLimit(8).setProtocol("https").setTileSize(256) + .setZoomLevelMax(18.toByte()).zoomLevelMin = 0.toByte() + setApiKey("P9roW0ePGY9TCRiXh8y5P1T4traxBrWl") } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/GeoItem.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/GeoItem.kt index f3c6bae3..55e01d98 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/GeoItem.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/GeoItem.kt @@ -14,38 +14,33 @@ * You should have received a copy of the GNU Lesser General Public License along with * this program. If not, see . */ +package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge -package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge; - -import org.mapsforge.core.model.LatLong; +import org.mapsforge.core.model.LatLong /** * Utility Class to handle GeoItem for ClusterMarker */ -public interface GeoItem { +interface GeoItem { /** * getLatLong * * @return item location in LatLong. */ - LatLong getLatLong(); + val latLong: LatLong /** * getTitle * * @return Title of the item, might be used as Caption text. */ - String getTitle(); - - boolean hasPhoto(); - - boolean ownPhoto(); + val title: String? + fun hasPhoto(): Boolean + fun ownPhoto(): Boolean /** * @return true if the station is active */ - boolean stationActive(); - - boolean isPendingUpload(); - + fun stationActive(): Boolean + val isPendingUpload: Boolean } \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/MapsforgeMapView.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/MapsforgeMapView.kt index 04c150aa..433d40f3 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/MapsforgeMapView.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/MapsforgeMapView.kt @@ -1,65 +1,59 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.util.AttributeSet; -import android.view.GestureDetector; -import android.view.MotionEvent; - -import org.mapsforge.map.android.view.MapView; - -public class MapsforgeMapView extends MapView { - - private final GestureDetector gestureDetector; - private MapDragListener onDragListener; - - public MapsforgeMapView(Context context, AttributeSet attributeSet) { - super(context, attributeSet); - - gestureDetector = new GestureDetector(context, new GestureListener()); +package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import android.view.GestureDetector +import android.view.GestureDetector.SimpleOnGestureListener +import android.view.MotionEvent +import org.mapsforge.map.android.view.MapView + +class MapsforgeMapView(context: Context?, attributeSet: AttributeSet?) : + MapView(context, attributeSet) { + private val gestureDetector: GestureDetector + private var onDragListener: MapDragListener? = null + + init { + gestureDetector = GestureDetector(context, GestureListener()) } @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent ev) { + override fun onTouchEvent(ev: MotionEvent): Boolean { try { - gestureDetector.onTouchEvent(ev); - return super.onTouchEvent(ev); - } catch (Exception ignored) { + gestureDetector.onTouchEvent(ev) + return super.onTouchEvent(ev) + } catch (ignored: Exception) { } - return false; + return false } - private class GestureListener extends GestureDetector.SimpleOnGestureListener { - @Override - public boolean onDoubleTap(MotionEvent e) { + private inner class GestureListener : SimpleOnGestureListener() { + override fun onDoubleTap(e: MotionEvent): Boolean { if (onDragListener != null) { - onDragListener.onDrag(); + onDragListener!!.onDrag() } - return true; + return true } - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, - float distanceX, float distanceY) { + override fun onScroll( + e1: MotionEvent?, e2: MotionEvent, + distanceX: Float, distanceY: Float + ): Boolean { if (onDragListener != null) { - onDragListener.onDrag(); + onDragListener!!.onDrag() } - return super.onScroll(e1, e2, distanceX, distanceY); + return super.onScroll(e1, e2, distanceX, distanceY) } } - public void setOnMapDragListener(MapDragListener onDragListener) { - this.onDragListener = onDragListener; + fun setOnMapDragListener(onDragListener: MapDragListener?) { + this.onDragListener = onDragListener } /** * Notifies the parent class when a MapView has been dragged */ - public interface MapDragListener { - - void onDrag(); - + interface MapDragListener { + fun onDrag() } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/MarkerBitmap.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/MarkerBitmap.kt index e8313e79..54b3c746 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/MarkerBitmap.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/MarkerBitmap.kt @@ -14,239 +14,149 @@ * You should have received a copy of the GNU Lesser General Public License along with * this program. If not, see . */ - -package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge; - -import android.content.Context; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.core.content.res.ResourcesCompat; - -import org.mapsforge.core.graphics.Bitmap; -import org.mapsforge.core.graphics.Paint; -import org.mapsforge.core.model.Point; -import org.mapsforge.map.model.DisplayModel; - -import java.lang.ref.WeakReference; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; +package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge + +import android.content.Context +import android.graphics.Color +import android.view.Gravity +import android.view.View.MeasureSpec +import android.view.ViewGroup +import android.widget.TextView +import androidx.core.content.res.ResourcesCompat +import de.bahnhoefe.deutschlands.bahnhofsfotos.R +import org.mapsforge.core.graphics.Bitmap +import org.mapsforge.core.graphics.Paint +import org.mapsforge.core.model.Point +import java.lang.ref.WeakReference +import java.util.function.Consumer /** * Utility Class to handle MarkerBitmap * it handles grid offset to display on the map with offset */ -public class MarkerBitmap { - - private static final Map captionViews = new HashMap<>(); - private static WeakReference contextRef; - - /** - * bitmap object for stations without photo - */ - private final Bitmap iconBmpWithoutPhoto; - - /** - * bitmap object for stations with photo - */ - private final Bitmap iconBmpWithPhoto; - - /** - * bitmap object for stations with photo from user - */ - private final Bitmap iconBmpOwnPhoto; - - /** - * bitmap object for inactive stations without photo - */ - private final Bitmap iconBmpWithoutPhotoInactive; - - /** - * bitmap object for inactive stations with photo - */ - private final Bitmap iconBmpWithPhotoInactive; - - /** - * bitmap object for inactive stations with photo from user - */ - private final Bitmap iconBmpOwnPhotoInactive; - - /** - * bitmap object for stations with photo from user with pending upload - */ - private final Bitmap iconBmpPendingUpload; - - /** - * Paint object for drawing icon - */ - private final Paint paint; - - /** - * offset grid of icon in Point. - * if you are using symmetric icon image, it should be half size of width&height. - * adjust this parameter to offset the axis of the image. - */ - private final Point iconOffset; - - /** - * maximum item size for the marker. - */ - private final int itemSizeMax; - - /** - * text size for icon - */ - private final float textSize; - - /** - * NOTE: all src* must be same bitmap size. - * - * @param srcWithoutPhoto source Bitmap object for stations without photo - * @param srcWithPhoto source Bitmap object for stations with photo - * @param srcOwnPhoto source Bitmap object for stations with photo from user - * @param srcWithoutPhotoInactive source Bitmap object for inactive stations without photo - * @param srcWithPhotoInactive source Bitmap object for inactive stations with photo - * @param srcOwnPhotoInactive source Bitmap object for inactive stations with photo from user - * @param srcPendingUpload source Bitmap object for stations with photo from user with pending upload - * @param grid grid point to be offset - * @param textSize text size for icon - * @param maxSize icon size threshold - */ - public MarkerBitmap(Context context, Bitmap srcWithoutPhoto, Bitmap srcWithPhoto, Bitmap srcOwnPhoto, - Bitmap srcWithoutPhotoInactive, Bitmap srcWithPhotoInactive, Bitmap srcOwnPhotoInactive, - Bitmap srcPendingUpload, - Point grid, float textSize, int maxSize, Paint paint) { - MarkerBitmap.contextRef = new WeakReference<>(context); - iconBmpWithoutPhoto = srcWithoutPhoto; - iconBmpWithPhoto = srcWithPhoto; - iconBmpOwnPhoto = srcOwnPhoto; - iconBmpWithPhotoInactive = srcWithPhotoInactive; - iconBmpWithoutPhotoInactive = srcWithoutPhotoInactive; - iconBmpOwnPhotoInactive = srcOwnPhotoInactive; - iconBmpPendingUpload = srcPendingUpload; - iconOffset = grid; - this.textSize = textSize * DisplayModel.getDeviceScaleFactor(); - itemSizeMax = maxSize; - this.paint = paint; - this.paint.setTextSize(getTextSize()); - } - - public MarkerBitmap(Context context, Bitmap bitmap, Point grid, float textSize, int maxSize, Paint paint) { - this(context, bitmap, bitmap, bitmap, bitmap, bitmap, bitmap, bitmap, grid, textSize, maxSize, paint); - } - - public static Bitmap getBitmapFromTitle(String title, Paint paint) { - var context = contextRef.get(); - if (!captionViews.containsKey(title) && context != null) { - var bubbleView = new TextView(context); - bubbleView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - Utils.setBackground(bubbleView, ResourcesCompat.getDrawable(context.getResources(), R.drawable.caption_background, null)); - bubbleView.setGravity(Gravity.CENTER); - bubbleView.setMaxEms(20); - bubbleView.setTextSize(10); - bubbleView.setPadding(5, -2, 5, -2); - bubbleView.setTextColor(android.graphics.Color.BLACK); - bubbleView.setText(title); - //Measure the view at the exact dimensions (otherwise the text won't center correctly) - int widthSpec = View.MeasureSpec.makeMeasureSpec(paint.getTextWidth(title), View.MeasureSpec.EXACTLY); - int heightSpec = View.MeasureSpec.makeMeasureSpec(paint.getTextHeight(title), View.MeasureSpec.EXACTLY); - bubbleView.measure(widthSpec, heightSpec); - - //Layout the view at the width and height - bubbleView.layout(0, 0, paint.getTextWidth(title), paint.getTextHeight(title)); - - captionViews.put(title, Utils.viewToBitmap(context, bubbleView)); - Objects.requireNonNull(captionViews.get(title)).incrementRefCount(); // FIXME: is never reduced! - } - return captionViews.get(title); +class MarkerBitmap( + context: Context, + private val iconBmpWithoutPhoto: Bitmap, + private val iconBmpWithPhoto: Bitmap, + private val iconBmpOwnPhoto: Bitmap, + private val iconBmpWithoutPhotoInactive: Bitmap, + private val iconBmpWithPhotoInactive: Bitmap, + private val iconBmpOwnPhotoInactive: Bitmap, + private val iconBmpPendingUpload: Bitmap, + val iconOffset: Point, + private val textSize: Float, + val itemMax: Int, + val paint: Paint +) { + init { + contextRef = WeakReference(context) + this.paint.setTextSize(this.textSize) } - protected static void clearCaptionBitmap() { - captionViews.values().forEach(Bitmap::decrementRefCount); - captionViews.clear(); - } + constructor( + context: Context, + bitmap: Bitmap, + grid: Point, + textSize: Float, + maxSize: Int, + paint: Paint + ) : this( + context, + bitmap, + bitmap, + bitmap, + bitmap, + bitmap, + bitmap, + bitmap, + grid, + textSize, + maxSize, + paint + ) /** * @return bitmap object according to the state of the stations */ - public Bitmap getBitmap(boolean hasPhoto, boolean ownPhoto, boolean stationActive, boolean inbox) { + fun getBitmap( + hasPhoto: Boolean, + ownPhoto: Boolean, + stationActive: Boolean, + inbox: Boolean + ): Bitmap { if (inbox) { - return iconBmpPendingUpload; + return iconBmpPendingUpload } - if (ownPhoto) { - if (stationActive) { - return iconBmpOwnPhoto; - } - return iconBmpOwnPhotoInactive; + return if (stationActive) { + iconBmpOwnPhoto + } else iconBmpOwnPhotoInactive } else if (hasPhoto) { - if (stationActive) { - return iconBmpWithPhoto; - } - return iconBmpWithPhotoInactive; - } - - if (stationActive) { - return iconBmpWithoutPhoto; + return if (stationActive) { + iconBmpWithPhoto + } else iconBmpWithPhotoInactive } - - return iconBmpWithoutPhotoInactive; + return if (stationActive) { + iconBmpWithoutPhoto + } else iconBmpWithoutPhotoInactive } - /** - * @return get offset of the icon - */ - public Point getIconOffset() { - return iconOffset; + fun decrementRefCounters() { + iconBmpOwnPhoto.decrementRefCount() + iconBmpWithPhoto.decrementRefCount() + iconBmpWithoutPhoto.decrementRefCount() + iconBmpOwnPhotoInactive.decrementRefCount() + iconBmpWithPhotoInactive.decrementRefCount() + iconBmpWithoutPhotoInactive.decrementRefCount() } - /** - * @return text size already adjusted with DisplayModel.getDeviceScaleFactor(), i.e. - * the scaling factor for fonts displayed on the display. - */ - public float getTextSize() { - return textSize; - } - - /** - * @return icon size threshold - */ - public int getItemMax() { - return itemSizeMax; - } - - /** - * @return Paint object for drawing icon - */ - public Paint getPaint() { - return paint; - } - - public void decrementRefCounters() { - if (iconBmpOwnPhoto != null) { - iconBmpOwnPhoto.decrementRefCount(); - } - if (iconBmpWithPhoto != null) { - iconBmpWithPhoto.decrementRefCount(); - } - if (iconBmpWithoutPhoto != null) { - iconBmpWithoutPhoto.decrementRefCount(); - } - if (iconBmpOwnPhotoInactive != null) { - iconBmpOwnPhotoInactive.decrementRefCount(); - } - if (iconBmpWithPhotoInactive != null) { - iconBmpWithPhotoInactive.decrementRefCount(); + companion object { + private val captionViews: MutableMap = HashMap() + private lateinit var contextRef: WeakReference + fun getBitmapFromTitle(title: String, paint: Paint): Bitmap? { + val context = contextRef.get() + if (!captionViews.containsKey(title) && context != null) { + val bubbleView = TextView(context) + bubbleView.layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + Utils.setBackground( + bubbleView, + ResourcesCompat.getDrawable( + context.resources, + R.drawable.caption_background, + null + ) + ) + bubbleView.gravity = Gravity.CENTER + bubbleView.maxEms = 20 + bubbleView.textSize = 10f + bubbleView.setPadding(5, -2, 5, -2) + bubbleView.setTextColor(Color.BLACK) + bubbleView.text = title + //Measure the view at the exact dimensions (otherwise the text won't center correctly) + val widthSpec = + MeasureSpec.makeMeasureSpec(paint.getTextWidth(title), MeasureSpec.EXACTLY) + val heightSpec = + MeasureSpec.makeMeasureSpec(paint.getTextHeight(title), MeasureSpec.EXACTLY) + bubbleView.measure(widthSpec, heightSpec) + + //Layout the view at the width and height + bubbleView.layout(0, 0, paint.getTextWidth(title), paint.getTextHeight(title)) + captionViews[title] = + Utils.viewToBitmap( + context, + bubbleView + ) + captionViews[title]?.incrementRefCount() // FIXME: is never reduced! + } + return captionViews[title] } - if (iconBmpWithoutPhotoInactive != null) { - iconBmpWithoutPhotoInactive.decrementRefCount(); + + fun clearCaptionBitmap() { + captionViews.values.forEach(Consumer { bitmap: Bitmap -> bitmap.decrementRefCount() }) + captionViews.clear() } } -} - - +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/TapHandler.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/TapHandler.kt index 87d7263f..642e5bd9 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/TapHandler.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/TapHandler.kt @@ -1,7 +1,5 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge; +package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge -public interface TapHandler { - - void onTap(T item); - -} +fun interface TapHandler { + fun onTap(item: T) +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/Utils.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/Utils.kt index 97eba574..b9fa4d3a 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/Utils.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/mapsforge/Utils.kt @@ -1,58 +1,47 @@ /* - * Copyright 2013-2014 Ludwig M Brinckmann - * Copyright 2014, 2015 devemux86 - * - * This program is free software: you can redistribute it and/or modify it under the - * terms of the GNU Lesser General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with - * this program. If not, see . + * Derived from https://github.com/mapsforge/mapsforge/blob/master/mapsforge-samples-android/src/main/java/org/mapsforge/samples/android/Utils.java */ -package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge; +package de.bahnhoefe.deutschlands.bahnhofsfotos.mapsforge -import android.content.Context; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.view.View; -import android.view.View.MeasureSpec; - -import org.mapsforge.core.graphics.Bitmap; -import org.mapsforge.map.android.graphics.AndroidGraphicFactory; +import android.content.Context +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.view.View +import android.view.View.MeasureSpec +import org.mapsforge.core.graphics.Bitmap +import org.mapsforge.map.android.graphics.AndroidGraphicFactory /** * Utility functions that can be used across different mapsforge based * activities. */ -public final class Utils { - - /** - * Compatibility method. - * - * @param view the view to set the background on - * @param background the background - */ - public static void setBackground(View view, Drawable background) { - view.setBackground(background); +class Utils private constructor() { + init { + throw IllegalStateException() } - public static Bitmap viewToBitmap(Context c, View view) { - view.measure(MeasureSpec.getSize(view.getMeasuredWidth()), - MeasureSpec.getSize(view.getMeasuredHeight())); - view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); - view.setDrawingCacheEnabled(true); - var drawable = new BitmapDrawable(c.getResources(), - android.graphics.Bitmap.createBitmap(view.getDrawingCache())); - view.setDrawingCacheEnabled(false); - return AndroidGraphicFactory.convertToBitmap(drawable); - } + companion object { + /** + * Compatibility method. + * + * @param view the view to set the background on + * @param background the background + */ + fun setBackground(view: View, background: Drawable?) { + view.background = background + } - private Utils() { - throw new IllegalStateException(); + fun viewToBitmap(c: Context, view: View): Bitmap { + view.measure( + MeasureSpec.getSize(view.measuredWidth), + MeasureSpec.getSize(view.measuredHeight) + ) + view.layout(0, 0, view.measuredWidth, view.measuredHeight) + val drawable = BitmapDrawable( + c.resources, + android.graphics.Bitmap.createBitmap(view.drawingCache) + ) + return AndroidGraphicFactory.convertToBitmap(drawable) + } } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/model/Country.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/model/Country.kt index ee9c0a3c..7e0893ea 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/model/Country.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/model/Country.kt @@ -14,7 +14,7 @@ data class Country @JvmOverloads constructor( ) : Serializable, Comparable { fun hasTimetableUrlTemplate(): Boolean { - return (timetableUrlTemplate != null) && timetableUrlTemplate.isNotEmpty() + return !timetableUrlTemplate.isNullOrEmpty() } val compatibleProviderApps: List diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/model/InboxResponse.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/model/InboxResponse.kt index 26248afb..959c165a 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/model/InboxResponse.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/model/InboxResponse.kt @@ -9,7 +9,7 @@ import de.bahnhoefe.deutschlands.bahnhofsfotos.R import java.lang.reflect.Type data class InboxResponse @JvmOverloads constructor( - @SerializedName("state") var _state: InboxResponseState? = InboxResponseState.ERROR, + @SerializedName("state") var inState: InboxResponseState? = InboxResponseState.ERROR, var message: String? = null, var id: Long? = null, var filename: String? = null, @@ -18,9 +18,9 @@ data class InboxResponse @JvmOverloads constructor( ) { var state: InboxResponseState - get() = _state ?: InboxResponseState.ERROR + get() = inState ?: InboxResponseState.ERROR set(value) { - _state = value + inState = value } @JsonAdapter(InboxResponseState.Serializer::class) diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofNotificationManager.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofNotificationManager.kt index 8332c7ce..882464dd 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofNotificationManager.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofNotificationManager.kt @@ -1,251 +1,260 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.notification; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.TaskStackBuilder; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; - -import java.util.Set; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.DetailsActivity; -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; -import de.bahnhoefe.deutschlands.bahnhofsfotos.UploadActivity; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Timetable; - -public abstract class NearbyBahnhofNotificationManager { - private static final int NOTIFICATION_ID = 1; - private static final int REQUEST_MAP = 0x10; - private static final int REQUEST_DETAIL = 0x20; - private static final int REQUEST_TIMETABLE = 0x30; - private static final int REQUEST_STATION = 0x40; - protected final String TAG = NearbyBahnhofNotificationManager.class.getSimpleName(); - - public static final String CHANNEL_ID = "bahnhoefe_channel_01";// The id of the channel. - - private static final String DB_BAHNHOF_LIVE_PKG = "de.deutschebahn.bahnhoflive"; - private static final String DB_BAHNHOF_LIVE_CLASS = "de.deutschebahn.bahnhoflive.MeinBahnhofActivity"; - private final Set countries; +package de.bahnhoefe.deutschlands.bahnhofsfotos.notification + +import android.Manifest +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.TaskStackBuilder +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import de.bahnhoefe.deutschlands.bahnhofsfotos.DetailsActivity +import de.bahnhoefe.deutschlands.bahnhofsfotos.R +import de.bahnhoefe.deutschlands.bahnhofsfotos.UploadActivity +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country.Companion.getCountryByCode +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.Timetable + +abstract class NearbyBahnhofNotificationManager( + context: Context, + station: Station, + distance: Double, + countries: Set +) { + protected val TAG: String = NearbyBahnhofNotificationManager::class.java.simpleName + private val countries: Set /** * The Bahnhof about which a notification is being built. */ - protected Station notificationStation; + var station: Station + protected set /** * The distance of the Bahnhof about which a notification is being built. */ - protected final double notificationDistance; + protected val notificationDistance: Double /** * The Android Context for which the notification is generated. */ - protected Context context; + protected var context: Context /** * Constructor. After construction, you need to call notifyUser for action to happen. - * - * @param context the Android Context from which this object ist created. - * @param station the station to issue a notification for. - * @param distance a double giving the distance from current location to bahnhof (in km) */ - public NearbyBahnhofNotificationManager(@NonNull Context context, @NonNull Station station, double distance, Set countries) { - this.context = context; - notificationDistance = distance; - this.notificationStation = station; - this.countries = countries; + init { + this.context = context + notificationDistance = distance + this.station = station + this.countries = countries } /** * Build a notification for a station with Photo. The start command. */ - public abstract void notifyUser(); + abstract fun notifyUser() /** * Called back once the notification was built up ready. */ - protected void onNotificationReady(Notification notification) { - if (context == null) { - return; // we're already destroyed - } - + protected fun onNotificationReady(notification: Notification?) { // Get an instance of the NotificationManager service - var notificationManager = NotificationManagerCompat.from(context); + val notificationManager = NotificationManagerCompat.from( + context + ) // Build the notification and issues it with notification manager. - notificationManager.notify(NOTIFICATION_ID, notification); + if (ActivityCompat.checkSelfPermission( + context, + Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + return + } + notificationManager.notify(NOTIFICATION_ID, notification!!) } - /** - * Helper method that configures a NotificationBuilder wtih the elements common to both - * notification types. - */ - protected NotificationCompat.Builder getBasicNotificationBuilder() { - // Build an intent for an action to see station details - var detailPendingIntent = getDetailPendingIntent(); - // Build an intent to see the station on a map - var mapPendingIntent = getMapPendingIntent(); - // Build an intent to view the station's timetable - var countryByCode = Country.getCountryByCode(countries, notificationStation.getCountry()); - var timetablePendingIntent = countryByCode.map(country -> getTimetablePendingIntent(country, notificationStation)).orElse(null); - - createChannel(context); - - // Texts and bigStyle - var textCreator = new TextCreator().invoke(); - var shortText = textCreator.getShortText(); - var bigStyle = textCreator.getBigStyle(); - - var builder = new NotificationCompat.Builder(context, CHANNEL_ID) + protected val basicNotificationBuilder: NotificationCompat.Builder + /** + * Helper method that configures a NotificationBuilder wtih the elements common to both + * notification types. + */ + get() { + // Build an intent for an action to see station details + val detailPendingIntent = detailPendingIntent + // Build an intent to see the station on a map + val mapPendingIntent = mapPendingIntent + // Build an intent to view the station's timetable + val countryByCode = getCountryByCode(countries, station.country) + val timetablePendingIntent = countryByCode.map { country: Country -> + getTimetablePendingIntent( + country, + station + ) + } + .orElse(null) + createChannel(context) + + // Texts and bigStyle + val textCreator = TextCreator().invoke() + val shortText = textCreator.shortText + val bigStyle = textCreator.bigStyle + var builder = NotificationCompat.Builder( + context, CHANNEL_ID + ) .setSmallIcon(R.drawable.ic_logotrain_found) .setContentTitle(context.getString(R.string.station_is_near)) .setContentText(shortText) .setContentIntent(detailPendingIntent) - .addAction(R.drawable.ic_directions_white_24dp, - context.getString(de.bahnhoefe.deutschlands.bahnhofsfotos.R.string.label_map), mapPendingIntent) + .addAction( + R.drawable.ic_directions_white_24dp, + context.getString(R.string.label_map), mapPendingIntent + ) .setStyle(bigStyle) .setPriority(NotificationCompat.PRIORITY_HIGH) .setOnlyAlertOnce(true) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); - - if (timetablePendingIntent != null) { - builder.addAction(R.drawable.ic_timetable, - context.getString(de.bahnhoefe.deutschlands.bahnhofsfotos.R.string.label_timetable), - timetablePendingIntent); - } - - builder = new NotificationCompat.Builder(context, CHANNEL_ID) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + if (timetablePendingIntent != null) { + builder.addAction( + R.drawable.ic_timetable, + context.getString(R.string.label_timetable), + timetablePendingIntent + ) + } + builder = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.ic_logotrain_found) .setContentTitle(context.getString(R.string.station_is_near)) .setContentText(shortText) .setContentIntent(detailPendingIntent) - .addAction(R.drawable.ic_directions_white_24dp, - context.getString(de.bahnhoefe.deutschlands.bahnhofsfotos.R.string.label_map), mapPendingIntent) - .addAction(R.drawable.ic_timetable, context.getString(de.bahnhoefe.deutschlands.bahnhofsfotos.R.string.label_timetable), timetablePendingIntent) + .addAction( + R.drawable.ic_directions_white_24dp, + context.getString(R.string.label_map), mapPendingIntent + ) + .addAction( + R.drawable.ic_timetable, + context.getString(R.string.label_timetable), + timetablePendingIntent + ) .setStyle(bigStyle) .setPriority(NotificationCompat.PRIORITY_HIGH) .setOnlyAlertOnce(true) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); - - return builder; - } - - public static void createChannel(Context context) { - var notificationManager = - (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.createNotificationChannel(new NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_name), NotificationManager.IMPORTANCE_DEFAULT)); - } + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + return builder + } - protected PendingIntent pendifyMe(Intent intent, int requestCode) { - var stackBuilder = TaskStackBuilder.create(context); - stackBuilder.addNextIntent(intent); + protected fun pendifyMe(intent: Intent?, requestCode: Int): PendingIntent { + val stackBuilder = TaskStackBuilder.create(context) + stackBuilder.addNextIntent(intent) try { - stackBuilder.addNextIntentWithParentStack(intent); // syntesize a back stack from the parent information in manifest - } catch (IllegalArgumentException iae) { + stackBuilder.addNextIntentWithParentStack(intent) // syntesize a back stack from the parent information in manifest + } catch (iae: IllegalArgumentException) { // unfortunately, this occurs if the supplied intent is not handled by our app // in this case, just add the the intent... - stackBuilder.addNextIntent(intent); + stackBuilder.addNextIntent(intent) } - return stackBuilder.getPendingIntent(requestCode, PendingIntent.FLAG_CANCEL_CURRENT); - } - - @NonNull - protected Intent getUploadActivity() { - // Build an intent for an action to see station details - var detailIntent = new Intent(context, UploadActivity.class); - detailIntent.putExtra(UploadActivity.EXTRA_STATION, notificationStation); - return detailIntent; - } - - @NonNull - protected PendingIntent getDetailPendingIntent() { - return pendifyMe(getUploadActivity(), REQUEST_DETAIL); + return stackBuilder.getPendingIntent(requestCode, PendingIntent.FLAG_CANCEL_CURRENT) } - /** - * Build an intent for an action to view a map. - * - * @return the PendingIntent built. - */ - protected PendingIntent getMapPendingIntent() { - var mapIntent = new Intent(Intent.ACTION_VIEW); - mapIntent.setData(Uri.parse("geo:" + notificationStation.getLat() + "," + notificationStation.getLon())); - return pendifyMe(mapIntent, REQUEST_MAP); - } - - /** - * Build an intent for an action to view a timetable for the station. - * - * @return the PendingIntent built. - */ - protected - @Nullable - PendingIntent getTimetablePendingIntent(Country country, Station station) { - var timetableIntent = new Timetable().createTimetableIntent(country, station); - if (timetableIntent != null) { - return pendifyMe(timetableIntent, NearbyBahnhofNotificationManager.REQUEST_TIMETABLE); + protected val uploadActivity: Intent + get() { + // Build an intent for an action to see station details + val detailIntent = Intent(context, UploadActivity::class.java) + detailIntent.putExtra(UploadActivity.Companion.EXTRA_STATION, station) + return detailIntent + } + private val detailPendingIntent: PendingIntent + get() = pendifyMe(uploadActivity, REQUEST_DETAIL) + private val mapPendingIntent: PendingIntent + /** + * Build an intent for an action to view a map. + * + * @return the PendingIntent built. + */ + get() { + val mapIntent = Intent(Intent.ACTION_VIEW) + mapIntent.data = Uri.parse("geo:" + station.lat + "," + station.lon) + return pendifyMe(mapIntent, REQUEST_MAP) } - return null; - } /** - * Build an intent for an action to view a map. + * Build an intent for an action to view a timetable for the station. * * @return the PendingIntent built. */ - @NonNull - protected PendingIntent getStationPendingIntent() { - // Build an intent for an action to see station details - var stationIntent = new Intent().setClassName(DB_BAHNHOF_LIVE_PKG, DB_BAHNHOF_LIVE_CLASS); - stationIntent.putExtra(DetailsActivity.EXTRA_STATION, notificationStation); - return pendifyMe(stationIntent, REQUEST_STATION); + private fun getTimetablePendingIntent(country: Country, station: Station?): PendingIntent? { + val timetableIntent = Timetable().createTimetableIntent(country, station) + return if (timetableIntent != null) { + pendifyMe(timetableIntent, REQUEST_TIMETABLE) + } else null } + protected val stationPendingIntent: PendingIntent + /** + * Build an intent for an action to view a map. + * + * @return the PendingIntent built. + */ + get() { + // Build an intent for an action to see station details + val stationIntent = Intent().setClassName(DB_BAHNHOF_LIVE_PKG, DB_BAHNHOF_LIVE_CLASS) + stationIntent.putExtra(DetailsActivity.Companion.EXTRA_STATION, station) + return pendifyMe(stationIntent, REQUEST_STATION) + } - public void destroy() { - NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID); - notificationStation = null; - context = null; - } - - public Station getStation() { - return notificationStation; + fun destroy() { + NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID) } - protected class TextCreator { - private String shortText; - private NotificationCompat.BigTextStyle bigStyle; - - public String getShortText() { - return shortText; - } - - public NotificationCompat.BigTextStyle getBigStyle() { - return bigStyle; + protected inner class TextCreator { + var shortText: String? = null + private set + var bigStyle: NotificationCompat.BigTextStyle? = null + private set + + operator fun invoke(): TextCreator { + shortText = context.getString( + R.string.template_short_text, + station.title, + notificationDistance + ) + val longText = context.getString( + R.string.template_long_text, + station.title, + notificationDistance, + if (station.hasPhoto()) context.getString(R.string.photo_exists) else "" + ) + bigStyle = NotificationCompat.BigTextStyle() + bigStyle!!.bigText(longText) + return this } + } - public TextCreator invoke() { - shortText = context.getString(R.string.template_short_text, notificationStation.getTitle(), notificationDistance); - var longText = context.getString(R.string.template_long_text, - notificationStation.getTitle(), - notificationDistance, - (notificationStation.hasPhoto() ? - context.getString(R.string.photo_exists) : - "")); - - bigStyle = new NotificationCompat.BigTextStyle(); - bigStyle.bigText(longText); - return this; + companion object { + private const val NOTIFICATION_ID = 1 + private const val REQUEST_MAP = 0x10 + private const val REQUEST_DETAIL = 0x20 + private const val REQUEST_TIMETABLE = 0x30 + private const val REQUEST_STATION = 0x40 + const val CHANNEL_ID = "bahnhoefe_channel_01" // The id of the channel. + private const val DB_BAHNHOF_LIVE_PKG = "de.deutschebahn.bahnhoflive" + private const val DB_BAHNHOF_LIVE_CLASS = "de.deutschebahn.bahnhoflive.MeinBahnhofActivity" + fun createChannel(context: Context?) { + val notificationManager = + context!!.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel( + NotificationChannel( + CHANNEL_ID, context.getString( + R.string.channel_name + ), NotificationManager.IMPORTANCE_DEFAULT + ) + ) } } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofNotificationManagerFactory.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofNotificationManagerFactory.kt index c05a6451..69373446 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofNotificationManagerFactory.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofNotificationManagerFactory.kt @@ -1,14 +1,10 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.notification; +package de.bahnhoefe.deutschlands.bahnhofsfotos.notification -import android.content.Context; - -import java.util.Set; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station; - -public class NearbyBahnhofNotificationManagerFactory { +import android.content.Context +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station +object NearbyBahnhofNotificationManagerFactory { /** * Construct the appropriate subclass of NearbyBahnhofNotificationManager for the given parameters. * @@ -17,11 +13,16 @@ public class NearbyBahnhofNotificationManagerFactory { * @param distance the distance of the station from current position of the user * @return an instance of NearbyBahnhofNotificationManager */ - static public NearbyBahnhofNotificationManager create(Context context, Station station, double distance, Set countries) { - if (station.hasPhoto()) { - return new NearbyBahnhofWithPhotoNotificationManager(context, station, distance, countries); + fun create( + context: Context, + station: Station, + distance: Double, + countries: Set + ): NearbyBahnhofNotificationManager { + return if (station.hasPhoto()) { + NearbyBahnhofWithPhotoNotificationManager(context, station, distance, countries) } else { - return new NearbyBahnhofWithoutPhotoNotificationManager(context, station, distance, countries); + NearbyBahnhofWithoutPhotoNotificationManager(context, station, distance, countries) } } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofWithPhotoNotificationManager.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofWithPhotoNotificationManager.kt index b86bb0f0..3c548899 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofWithPhotoNotificationManager.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofWithPhotoNotificationManager.kt @@ -1,74 +1,63 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.notification; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.util.Log; - -import androidx.annotation.Nullable; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.core.app.NotificationCompat; - -import java.util.Set; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.BitmapAvailableHandler; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.BitmapCache; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.ConnectionUtil; - -public class NearbyBahnhofWithPhotoNotificationManager extends NearbyBahnhofNotificationManager implements BitmapAvailableHandler { - - private static final long[] VIBRATION_PATTERN = new long[]{300}; - private static final int LED_COLOR = 0x00ff0000; - public static final int BITMAP_HEIGHT = 400; - public static final int BITMAP_WIDTH = 400; - - public NearbyBahnhofWithPhotoNotificationManager(Context context, Station station, double distance, Set countries) { - super(context, station, distance, countries); - Log.d(TAG, "Creating " + getClass().getSimpleName()); +package de.bahnhoefe.deutschlands.bahnhofsfotos.notification + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.util.Log +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.app.NotificationCompat +import de.bahnhoefe.deutschlands.bahnhofsfotos.R +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.BitmapAvailableHandler +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.BitmapCache +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.ConnectionUtil + +class NearbyBahnhofWithPhotoNotificationManager( + context: Context, + station: Station, + distance: Double, + countries: Set? +) : NearbyBahnhofNotificationManager(context, station, distance, countries), + BitmapAvailableHandler { + init { + Log.d(TAG, "Creating " + javaClass.simpleName) } /** * Build a notification for a station with Photo */ - @Override - public void notifyUser() { - if (ConnectionUtil.checkInternetConnection(context)) { - BitmapCache.getInstance().getPhoto(this, notificationStation.getPhotoUrl()); + override fun notifyUser() { + if (ConnectionUtil.checkInternetConnection(context!!)) { + BitmapCache.Companion.getInstance().getPhoto(this, notificationStation.photoUrl) } } - /** * This gets called if the requested bitmap is available. Finish and issue the notification. * * @param bitmap the fetched Bitmap for the notification. May be null */ - @Override - public void onBitmapAvailable(@Nullable Bitmap bitmap) { + override fun onBitmapAvailable(bitmap: Bitmap?) { + var bitmap = bitmap if (context == null) { - return; // we're already destroyed + return // we're already destroyed } - if (bitmap == null) { - bitmap = getBitmapFromResource(R.drawable.ic_stations_with_photo); + bitmap = getBitmapFromResource(R.drawable.ic_stations_with_photo) } - - var bigPictureStyle = new NotificationCompat.BigPictureStyle(); + val bigPictureStyle = NotificationCompat.BigPictureStyle() if (bitmap != null) { - bigPictureStyle.bigPicture(bitmap).setBigContentTitle(null).setSummaryText(notificationStation.getLicense()); + bigPictureStyle.bigPicture(bitmap).setBigContentTitle(null) + .setSummaryText(notificationStation.license) } - - var notificationBuilder = getBasicNotificationBuilder() - .setStyle(bigPictureStyle) - .extend(new NotificationCompat.WearableExtender()) - .setVibrate(VIBRATION_PATTERN) - .setColor(LED_COLOR); - - onNotificationReady(notificationBuilder.build()); + val notificationBuilder = basicNotificationBuilder + .setStyle(bigPictureStyle) + .extend(NotificationCompat.WearableExtender()) + .setVibrate(VIBRATION_PATTERN) + .setColor(LED_COLOR) + onNotificationReady(notificationBuilder.build()) } /** @@ -77,15 +66,20 @@ public class NearbyBahnhofWithPhotoNotificationManager extends NearbyBahnhofNoti * @param id the resource ID denoting a drawable resource * @return the Bitmap. May be null. */ - private Bitmap getBitmapFromResource(int id) { - var vectorDrawable = AppCompatResources.getDrawable(context, id); - assert vectorDrawable != null; - vectorDrawable.setBounds(0, 0, BITMAP_WIDTH, BITMAP_HEIGHT); - var bm = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888); - var canvas = new Canvas(bm); - canvas.drawColor(Color.WHITE); - vectorDrawable.draw(canvas); - return bm; + private fun getBitmapFromResource(id: Int): Bitmap { + val vectorDrawable = AppCompatResources.getDrawable(context!!, id)!! + vectorDrawable.setBounds(0, 0, BITMAP_WIDTH, BITMAP_HEIGHT) + val bm = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bm) + canvas.drawColor(Color.WHITE) + vectorDrawable.draw(canvas) + return bm } + companion object { + private val VIBRATION_PATTERN = longArrayOf(300) + private const val LED_COLOR = 0x00ff0000 + const val BITMAP_HEIGHT = 400 + const val BITMAP_WIDTH = 400 + } } \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofWithoutPhotoNotificationManager.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofWithoutPhotoNotificationManager.kt index 9a95017a..c4ea6638 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofWithoutPhotoNotificationManager.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/notification/NearbyBahnhofWithoutPhotoNotificationManager.kt @@ -1,50 +1,51 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.notification; - -import android.app.PendingIntent; -import android.content.Context; -import android.util.Log; - -import java.util.Set; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station; - -public class NearbyBahnhofWithoutPhotoNotificationManager extends NearbyBahnhofNotificationManager { - - private static final long[] VIBRATION_PATTERN = new long[]{300, 100, 300, 100, 300}; - public static final int LED_COLOR = 0x0000ffff; - private static final int REQUEST_FOTO = 0x100; - - public NearbyBahnhofWithoutPhotoNotificationManager(Context context, Station station, double distance, Set countries) { - super(context, station, distance, countries); - Log.d(TAG, "Creating " + getClass().getSimpleName()); +package de.bahnhoefe.deutschlands.bahnhofsfotos.notification + +import android.app.PendingIntent +import android.content.Context +import android.util.Log +import de.bahnhoefe.deutschlands.bahnhofsfotos.R +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station + +class NearbyBahnhofWithoutPhotoNotificationManager( + context: Context, + station: Station, + distance: Double, + countries: Set? +) : NearbyBahnhofNotificationManager(context, station, distance, countries) { + init { + Log.d(TAG, "Creating " + javaClass.simpleName) } - // helpers that create notification elements that are common to "with foto" and "without foto" - private PendingIntent getFotoPendingIntent() { - // Build an intent for an action to take a picture - // actually this launches UploadActivity with a specific Extra that causes it to launch - // Photo immediately. - var intent = getUploadActivity(); - return pendifyMe(intent, REQUEST_FOTO); - } + private val fotoPendingIntent: PendingIntent? + // helpers that create notification elements that are common to "with foto" and "without foto" + private get() { + // Build an intent for an action to take a picture + // actually this launches UploadActivity with a specific Extra that causes it to launch + // Photo immediately. + val intent = uploadActivity + return pendifyMe(intent, REQUEST_FOTO) + } /** * Build a notification for a station without photo. Call onNotificationReady if done. */ - @Override - public void notifyUser() { - var notificationBuilder = getBasicNotificationBuilder(); - - var fotoPendingIntent = getFotoPendingIntent(); - + override fun notifyUser() { + val notificationBuilder = basicNotificationBuilder + val fotoPendingIntent = fotoPendingIntent notificationBuilder - .addAction(R.drawable.ic_photo_camera_white_48px, context.getString(R.string.photo), fotoPendingIntent) - .setVibrate(VIBRATION_PATTERN) - .setColor(LED_COLOR); - - onNotificationReady(notificationBuilder.build()); + .addAction( + R.drawable.ic_photo_camera_white_48px, + context!!.getString(R.string.photo), + fotoPendingIntent + ) + .setVibrate(VIBRATION_PATTERN).color = LED_COLOR + onNotificationReady(notificationBuilder!!.build()) } + companion object { + private val VIBRATION_PATTERN = longArrayOf(300, 100, 300, 100, 300) + const val LED_COLOR = 0x0000ffff + private const val REQUEST_FOTO = 0x100 + } } \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/rsapi/RSAPI.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/rsapi/RSAPI.kt index 7bdb8255..feff4193 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/rsapi/RSAPI.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/rsapi/RSAPI.kt @@ -1,109 +1,122 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi; - -import java.util.List; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ChangePassword; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.HighScore; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxResponse; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxStateQuery; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PhotoStations; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProblemReport; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Profile; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PublicInbox; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Statistic; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Token; -import okhttp3.RequestBody; -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.DELETE; -import retrofit2.http.Field; -import retrofit2.http.FormUrlEncoded; -import retrofit2.http.GET; -import retrofit2.http.Header; -import retrofit2.http.Headers; -import retrofit2.http.POST; -import retrofit2.http.Path; -import retrofit2.http.Query; - -public interface RSAPI { - - String TAG = RSAPI.class.getSimpleName(); - - @GET("/countries") - Call> getCountries(); +package de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi + +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ChangePassword +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.HighScore +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxResponse +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxStateQuery +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PhotoStations +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProblemReport +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Profile +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PublicInbox +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Statistic +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Token +import okhttp3.RequestBody +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.Field +import retrofit2.http.FormUrlEncoded +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Headers +import retrofit2.http.POST +import retrofit2.http.Path +import retrofit2.http.Query + +interface RSAPI { + @get:GET("/countries") + val countries: Call?>? @GET("/stats") - Call getStatistic(@Query("country") String country); + fun getStatistic(@Query("country") country: String?): Call? @GET("/photographers") - Call getHighScore(@Query("country") String country); + fun getHighScore(@Query("country") country: String?): Call? - @GET("/photographers") - Call getHighScore(); + @get:GET("/photographers") + val highScore: Call? @GET("/myProfile") - Call getProfile(@Header("Authorization") String authorization); + fun getProfile(@Header("Authorization") authorization: String?): Call? - @Headers({ - "Content-Type: application/json" - }) + @Headers("Content-Type: application/json") @POST("/myProfile") - Call saveProfile(@Header("Authorization") String authorization, @Body Profile profile); - - @Headers({ - "Content-Type: application/json" - }) + fun saveProfile( + @Header("Authorization") authorization: String?, + @Body profile: Profile? + ): Call? + @Headers("Content-Type: application/json") @POST("/changePassword") - Call changePassword(@Header("Authorization") String authorization, @Body ChangePassword changePassword); + fun changePassword( + @Header("Authorization") authorization: String?, + @Body changePassword: ChangePassword? + ): Call? @POST("/photoUpload") - Call photoUpload(@Header("Authorization") String authorization, - @Header("Station-Id") String stationId, - @Header("Country") String countryCode, - @Header("Station-Title") String stationTitle, - @Header("Latitude") Double latitude, - @Header("Longitude") Double longitude, - @Header("Comment") String comment, - @Header("Active") Boolean active, - @Body RequestBody file); - - @Headers({ - "Content-Type: application/json" - }) + fun photoUpload( + @Header("Authorization") authorization: String?, + @Header("Station-Id") stationId: String?, + @Header("Country") countryCode: String?, + @Header("Station-Title") stationTitle: String?, + @Header("Latitude") latitude: Double?, + @Header("Longitude") longitude: Double?, + @Header("Comment") comment: String?, + @Header("Active") active: Boolean?, + @Body file: RequestBody? + ): Call? + + @Headers("Content-Type: application/json") @POST("/userInbox") - Call> queryUploadState(@Header("Authorization") String authorization, - @Body List inboxStateQueries); + fun queryUploadState( + @Header("Authorization") authorization: String?, + @Body inboxStateQueries: List? + ): Call?>? - @Headers({ - "Content-Type: application/json" - }) + @Headers("Content-Type: application/json") @POST("/reportProblem") - Call reportProblem(@Header("Authorization") String authorization, - @Body ProblemReport problemReport); + fun reportProblem( + @Header("Authorization") authorization: String?, + @Body problemReport: ProblemReport? + ): Call? - @GET("/publicInbox") - Call> getPublicInbox(); + @get:GET("/publicInbox") + val publicInbox: Call?>? @POST("/resendEmailVerification") - Call resendEmailVerification(@Header("Authorization") String authorization); + fun resendEmailVerification(@Header("Authorization") authorization: String?): Call? @GET("/photoStationById/{country}/{id}") - Call getPhotoStationById(@Path("country") String country, @Path("id") String id); + fun getPhotoStationById( + @Path("country") country: String?, + @Path("id") id: String? + ): Call? @GET("/photoStationsByCountry/{country}") - Call getPhotoStationsByCountry(@Path("country") String country); + fun getPhotoStationsByCountry(@Path("country") country: String?): Call @FormUrlEncoded @POST("/oauth2/token") - Call requestAccessToken(@Field("code") String code, @Field("client_id") String clientId, @Field("grant_type") String grantType, @Field("redirect_uri") String redirectUri, @Field("code_verifier") String codeVerifier); + fun requestAccessToken( + @Field("code") code: String?, + @Field("client_id") clientId: String?, + @Field("grant_type") grantType: String?, + @Field("redirect_uri") redirectUri: String?, + @Field("code_verifier") codeVerifier: String? + ): Call? @FormUrlEncoded @POST("/oauth2/revoke") - Call revokeToken(@Field("token") String accessToken, @Field("token_type_hint") String tokenTypeHint); + fun revokeToken( + @Field("token") accessToken: String?, + @Field("token_type_hint") tokenTypeHint: String? + ): Call? @DELETE("/myProfile") - Call deleteAccount(@Header("Authorization") String authorization); + fun deleteAccount(@Header("Authorization") authorization: String?): Call? -} + companion object { + val TAG = RSAPI::class.java.simpleName + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/rsapi/RSAPIClient.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/rsapi/RSAPIClient.kt index 46599f52..237f5b5c 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/rsapi/RSAPIClient.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/rsapi/RSAPIClient.kt @@ -1,286 +1,310 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi; - -import android.content.Context; -import android.net.Uri; -import android.os.Build; -import android.util.Log; -import android.widget.Toast; - -import androidx.annotation.NonNull; - -import com.google.gson.GsonBuilder; - -import java.io.IOException; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.BaseApplication; -import de.bahnhoefe.deutschlands.bahnhofsfotos.BuildConfig; -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ChangePassword; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.HighScore; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxResponse; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxStateQuery; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.License; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PhotoStations; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProblemReport; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Profile; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PublicInbox; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Statistic; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Token; -import de.bahnhoefe.deutschlands.bahnhofsfotos.util.PKCEUtil; -import okhttp3.Interceptor; -import okhttp3.OkHttpClient; -import okhttp3.RequestBody; -import okhttp3.logging.HttpLoggingInterceptor; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -public class RSAPIClient { - - private static final String TAG = RSAPIClient.class.getSimpleName(); - private final String redirectUri; - private RSAPI api; - private String baseUrl; - private final String clientId; - private Token token; - private PKCEUtil pkce; - - public RSAPIClient(String baseUrl, String clientId, String accessToken, String redirectUri) { - this.baseUrl = baseUrl; - this.clientId = clientId; - this.redirectUri = redirectUri; +package de.bahnhoefe.deutschlands.bahnhofsfotos.rsapi + +import android.content.Context +import android.net.Uri +import android.os.Build +import android.util.Log +import android.widget.Toast +import com.google.gson.GsonBuilder +import de.bahnhoefe.deutschlands.bahnhofsfotos.BaseApplication +import de.bahnhoefe.deutschlands.bahnhofsfotos.BuildConfig +import de.bahnhoefe.deutschlands.bahnhofsfotos.R +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ChangePassword +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.HighScore +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.HighScore.HighScoreDeserializer +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxResponse +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.InboxStateQuery +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.License +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.License.LicenseDeserializer +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PhotoStations +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.ProblemReport +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Profile +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.PublicInbox +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Statistic +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Token +import de.bahnhoefe.deutschlands.bahnhofsfotos.util.PKCEUtil +import okhttp3.Interceptor +import okhttp3.Interceptor.Chain.proceed +import okhttp3.Interceptor.Chain.request +import okhttp3.OkHttpClient.Builder.addInterceptor +import okhttp3.OkHttpClient.Builder.build +import okhttp3.Request.Builder.build +import okhttp3.Request.Builder.header +import okhttp3.Request.newBuilder +import okhttp3.RequestBody +import okhttp3.Response +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.io.IOException +import java.security.NoSuchAlgorithmException +import java.util.Locale +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger +import java.util.function.Consumer + +class RSAPIClient( + private var baseUrl: String, + private val clientId: String, + accessToken: String?, + val redirectUri: String +) { + private var api: RSAPI + private var token: Token? = null + private var pkce: PKCEUtil? = null + + init { if (accessToken != null) { - this.token = new Token( - accessToken, - "Bearer"); + token = Token( + accessToken, + "Bearer" + ) } - api = createRSAPI(); + api = createRSAPI() } - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - this.api = createRSAPI(); + fun setBaseUrl(baseUrl: String) { + this.baseUrl = baseUrl + api = createRSAPI() } - private String getPkceCodeChallenge() throws NoSuchAlgorithmException { - pkce = new PKCEUtil(); - return pkce.getCodeChallenge(); - } - - private String getPkceCodeVerifier() { - return pkce != null ? pkce.getCodeVerifier() : null; - } - - private RSAPI createRSAPI() { - var gson = new GsonBuilder(); - gson.registerTypeAdapter(HighScore.class, new HighScore.HighScoreDeserializer()); - gson.registerTypeAdapter(License.class, new License.LicenseDeserializer()); - - var builder = new OkHttpClient.Builder() - .addInterceptor(new UserAgentInterceptor()) - .addInterceptor(new AcceptLanguageInterceptor()); - + @get:Throws(NoSuchAlgorithmException::class) + private val pkceCodeChallenge: String? + private get() { + pkce = PKCEUtil() + return pkce.getCodeChallenge() + } + private val pkceCodeVerifier: String? + private get() = if (pkce != null) pkce.getCodeVerifier() else null + + private fun createRSAPI(): RSAPI { + val gson = GsonBuilder() + gson.registerTypeAdapter(HighScore::class.java, HighScoreDeserializer()) + gson.registerTypeAdapter(License::class.java, LicenseDeserializer()) + val builder: Builder = Builder() + .addInterceptor(UserAgentInterceptor()) + .addInterceptor(AcceptLanguageInterceptor()) if (BuildConfig.DEBUG) { - var loggingInterceptor = new HttpLoggingInterceptor(); - loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); - builder.addInterceptor(loggingInterceptor); + val loggingInterceptor = HttpLoggingInterceptor() + loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY) + builder.addInterceptor(loggingInterceptor) } - - Retrofit retrofit = new Retrofit.Builder() - .baseUrl(baseUrl) - .client(builder.build()) - .addConverterFactory(GsonConverterFactory.create(gson.create())) - .build(); - - return retrofit.create(RSAPI.class); + val retrofit = Retrofit.Builder() + .baseUrl(baseUrl) + .client(builder.build()) + .addConverterFactory(GsonConverterFactory.create(gson.create())) + .build() + return retrofit.create(RSAPI::class.java) } - private static class AcceptLanguageInterceptor implements Interceptor { - - @NonNull - @Override - public okhttp3.Response intercept(@NonNull final Chain chain) throws IOException { - return chain.proceed(chain.request().newBuilder() + private class AcceptLanguageInterceptor : Interceptor { + @Throws(IOException::class) + override fun intercept(chain: Chain): Response { + return chain.proceed( + chain.request().newBuilder() .header("Accept-Language", Locale.getDefault().toLanguageTag()) - .build()); + .build() + ) } - } /** * This interceptor adds a custom User-Agent. */ - public static class UserAgentInterceptor implements Interceptor { - - private final String USER_AGENT = BuildConfig.APPLICATION_ID + "/" + BuildConfig.VERSION_NAME + "(" + BuildConfig.VERSION_CODE + "); Android " + Build.VERSION.RELEASE + "/" + Build.VERSION.SDK_INT; - - @Override - @NonNull - public okhttp3.Response intercept(Interceptor.Chain chain) throws IOException { - return chain.proceed(chain.request().newBuilder() + class UserAgentInterceptor : Interceptor { + private val USER_AGENT = + BuildConfig.APPLICATION_ID + "/" + BuildConfig.VERSION_NAME + "(" + BuildConfig.VERSION_CODE + "); Android " + Build.VERSION.RELEASE + "/" + Build.VERSION.SDK_INT + + @Throws(IOException::class) + override fun intercept(chain: Chain): Response { + return chain.proceed( + chain.request().newBuilder() .header("User-Agent", USER_AGENT) - .build()); + .build() + ) } } - public Call> getCountries() { - return api.getCountries(); - } - - public void runUpdateCountriesAndStations(Context context, BaseApplication baseApplication, ResultListener listener) { - getCountries().enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call> call, @NonNull Response> response) { - var body = response.body(); - if (response.isSuccessful() && body != null) { - baseApplication.getDbAdapter().insertCountries(body); + val countries: Call?>? + get() = api.countries + + fun runUpdateCountriesAndStations( + context: Context, + baseApplication: BaseApplication?, + listener: (Boolean) -> Unit + ) { + countries!!.enqueue(object : Callback?> { + override fun onResponse( + call: Call?>, + response: retrofit2.Response?> + ) { + val body = response.body() + if (response.isSuccessful && body != null) { + baseApplication.getDbAdapter().insertCountries(body) } } - @Override - public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - Log.e(TAG, "Error refreshing countries", t); - Toast.makeText(context, context.getString(R.string.error_updating_countries) + t.getMessage(), Toast.LENGTH_LONG).show(); + override fun onFailure(call: Call?>, t: Throwable) { + Log.e(TAG, "Error refreshing countries", t) + Toast.makeText( + context, + context.getString(R.string.error_updating_countries) + t.message, + Toast.LENGTH_LONG + ).show() } - }); - - var countryCodes = baseApplication.getCountryCodes(); - var overallSuccess = new AtomicBoolean(true); - var runningRequestCount = new AtomicInteger(countryCodes.size()); - countryCodes.forEach(countryCode -> api.getPhotoStationsByCountry(countryCode).enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - var stationList = response.body(); - if (response.isSuccessful() && stationList != null) { - baseApplication.getDbAdapter().insertStations(stationList, countryCode); - baseApplication.setLastUpdate(System.currentTimeMillis()); + }) + val countryCodes = baseApplication.getCountryCodes() + val overallSuccess = AtomicBoolean(true) + val runningRequestCount = AtomicInteger( + countryCodes!!.size + ) + countryCodes!!.forEach(Consumer { countryCode: String? -> + api.getPhotoStationsByCountry(countryCode).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: retrofit2.Response + ) { + val stationList = response.body() + if (response.isSuccessful && stationList != null) { + baseApplication.getDbAdapter().insertStations(stationList, countryCode) + baseApplication.setLastUpdate(System.currentTimeMillis()) + } + if (!response.isSuccessful) { + overallSuccess.set(false) + } + onResult(response.isSuccessful) } - if (!response.isSuccessful()) { - overallSuccess.set(false); - } - onResult(response.isSuccessful()); - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Log.e(TAG, "Error refreshing stations", t); - Toast.makeText(context, context.getString(R.string.station_update_failed) + t.getMessage(), Toast.LENGTH_LONG).show(); - onResult(false); - } - void onResult(boolean success) { - var stillRunningRequests = runningRequestCount.decrementAndGet(); - if (!success) { - overallSuccess.set(false); + override fun onFailure(call: Call, t: Throwable) { + Log.e(TAG, "Error refreshing stations", t) + Toast.makeText( + context, + context.getString(R.string.station_update_failed) + t.message, + Toast.LENGTH_LONG + ).show() + onResult(false) } - if (stillRunningRequests == 0) { - listener.onResult(overallSuccess.get()); - } - } - })); - - } - public Call getPhotoStationById(String country, String id) { - return api.getPhotoStationById(country, id); + fun onResult(success: Boolean) { + val stillRunningRequests = runningRequestCount.decrementAndGet() + if (!success) { + overallSuccess.set(false) + } + if (stillRunningRequests == 0) { + listener.onResult(overallSuccess.get()) + } + } + }) + }) } - public Call getPhotoStationsByCountry(String country) { - return api.getPhotoStationsByCountry(country); + fun getPhotoStationById(country: String?, id: String?): Call? { + return api.getPhotoStationById(country, id) } - public Call> getPublicInbox() { - return api.getPublicInbox(); + fun getPhotoStationsByCountry(country: String?): Call? { + return api.getPhotoStationsByCountry(country) } - public Call getHighScore() { - return api.getHighScore(); - } + val publicInbox: Call?>? + get() = api.publicInbox + val highScore: Call? + get() = api.highScore - public Call getHighScore(String country) { - return api.getHighScore(country); + fun getHighScore(country: String?): Call? { + return api.getHighScore(country) } - public Call reportProblem(ProblemReport problemReport) { - return api.reportProblem(getUserAuthorization(), problemReport); + fun reportProblem(problemReport: ProblemReport?): Call? { + return api.reportProblem(userAuthorization, problemReport) } - private String getUserAuthorization() { - if (hasToken()) { - return token.getTokenType() + " " + token.getAccessToken(); - } - return null; + private val userAuthorization: String? + private get() = if (hasToken()) { + token!!.tokenType + " " + token!!.accessToken + } else null + + fun photoUpload( + stationId: String?, countryCode: String?, + stationTitle: String?, latitude: Double?, + longitude: Double?, comment: String?, + active: Boolean?, file: RequestBody? + ): Call? { + return api.photoUpload( + userAuthorization, + stationId, + countryCode, + stationTitle, + latitude, + longitude, + comment, + active, + file + ) } - public Call photoUpload(String stationId, String countryCode, - String stationTitle, Double latitude, - Double longitude, String comment, - Boolean active, RequestBody file) { - return api.photoUpload(getUserAuthorization(), stationId, countryCode, stationTitle, latitude, longitude, comment, active, file); + fun queryUploadState(stateQueries: List?): Call?>? { + return api.queryUploadState(userAuthorization, stateQueries) } - public Call> queryUploadState(List stateQueries) { - return api.queryUploadState(getUserAuthorization(), stateQueries); - } + val profile: Call? + get() = api.getProfile(userAuthorization) - public Call getProfile() { - return api.getProfile(getUserAuthorization()); + fun saveProfile(profile: Profile?): Call? { + return api.saveProfile(userAuthorization, profile) } - public Call saveProfile(Profile profile) { - return api.saveProfile(getUserAuthorization(), profile); + fun changePassword(newPassword: String?): Call? { + return api.changePassword(userAuthorization, ChangePassword(newPassword!!)) } - public Call changePassword(String newPassword) { - return api.changePassword(getUserAuthorization(), new ChangePassword(newPassword)); + fun deleteAccount(): Call? { + return api.deleteAccount(userAuthorization) } - public Call deleteAccount() { - return api.deleteAccount(getUserAuthorization()); + fun resendEmailVerification(): Call? { + return api.resendEmailVerification(userAuthorization) } - public Call resendEmailVerification() { - return api.resendEmailVerification(getUserAuthorization()); + fun getStatistic(country: String?): Call? { + return api.getStatistic(country) } - public Call getStatistic(String country) { - return api.getStatistic(country); + fun requestAccessToken(code: String?): Call? { + return api.requestAccessToken( + code, + clientId, + "authorization_code", + redirectUri, + pkceCodeVerifier + ) } - public Call requestAccessToken(String code) { - return api.requestAccessToken(code, clientId, "authorization_code", redirectUri, getPkceCodeVerifier()); + fun setToken(token: Token?) { + this.token = token } - public void setToken(Token token) { - this.token = token; + fun hasToken(): Boolean { + return token != null } - public boolean hasToken() { - return token != null; + fun clearToken() { + token = null } - public void clearToken() { - this.token = null; + @Throws(NoSuchAlgorithmException::class) + fun createAuthorizeUri(): Uri { + return Uri.parse(baseUrl + "oauth2/authorize" + "?client_id=" + clientId + "&code_challenge=" + pkceCodeChallenge + "&code_challenge_method=S256&scope=all&response_type=code&redirect_uri=" + redirectUri) } - public Uri createAuthorizeUri() throws NoSuchAlgorithmException { - return Uri.parse(baseUrl + "oauth2/authorize" + "?client_id=" + clientId + "&code_challenge=" + getPkceCodeChallenge() + "&code_challenge_method=S256&scope=all&response_type=code&redirect_uri=" + redirectUri); + interface ResultListener { + fun onResult(success: Boolean) } - public String getRedirectUri() { - return redirectUri; + companion object { + private val TAG = RSAPIClient::class.java.simpleName } - - public interface ResultListener { - void onResult(boolean success); - } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/BitmapAvailableHandler.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/BitmapAvailableHandler.kt index 10f6c207..7da4da6a 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/BitmapAvailableHandler.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/BitmapAvailableHandler.kt @@ -1,18 +1,15 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.util; +package de.bahnhoefe.deutschlands.bahnhofsfotos.util -import android.graphics.Bitmap; - -import androidx.annotation.Nullable; +import android.graphics.Bitmap /** * Callback for BitmapDownloader */ -public interface BitmapAvailableHandler { - +interface BitmapAvailableHandler { /** * This gets called if the requested bitmap is available. Finish and issue the notification. * * @param bitmap the fetched Bitmap for the notification. May be null */ - void onBitmapAvailable(@Nullable Bitmap bitmap); -} + fun onBitmapAvailable(bitmap: Bitmap?) +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/BitmapCache.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/BitmapCache.kt index b8957f44..a5a44157 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/BitmapCache.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/BitmapCache.kt @@ -1,50 +1,24 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.util; +package de.bahnhoefe.deutschlands.bahnhofsfotos.util -import android.graphics.Bitmap; -import android.util.Log; - -import androidx.annotation.NonNull; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; +import android.graphics.Bitmap +import android.util.Log +import java.net.MalformedURLException +import java.net.URL +import java.util.function.Consumer /** * A cache for station photos. */ -public class BitmapCache { - private final String TAG = BitmapCache.class.getSimpleName(); - - /** - * The singleton. - */ - private static BitmapCache instance = null; - - /** - * Get the BitmapCache instance. - * - * @return the single instance of BitmapCache. - */ - public static BitmapCache getInstance() { - synchronized (BitmapCache.class) { - if (instance == null) { - instance = new BitmapCache(); - } - return instance; - } - } +class BitmapCache private constructor() { + private val TAG = BitmapCache::class.java.simpleName + private val requests: HashMap> + private val cache: HashMap - private BitmapCache() { - cache = new HashMap<>(10); - requests = new HashMap<>(3); + init { + cache = HashMap(10) + requests = HashMap(3) } - private final HashMap> requests; - - private final HashMap cache; - /** * Get a picture for the given URL, either from cache or by downloading. * The fetching happens asynchronously. When finished, the provided callback interface is called. @@ -52,14 +26,14 @@ public class BitmapCache { * @param callback the BitmapAvailableHandler to call on completion. * @param resourceUrl the URL to fetch */ - public void getPhoto(BitmapAvailableHandler callback, @NonNull String resourceUrl) { - URL url = null; + fun getPhoto(callback: BitmapAvailableHandler, resourceUrl: String) { + var url: URL? = null try { - url = new URL(resourceUrl); - getPhoto(callback, url); - } catch (MalformedURLException e) { - Log.e(TAG, "Couldn't load photo from malformed URL " + resourceUrl); - callback.onBitmapAvailable(null); + url = URL(resourceUrl) + getPhoto(callback, url) + } catch (e: MalformedURLException) { + Log.e(TAG, "Couldn't load photo from malformed URL $resourceUrl") + callback.onBitmapAvailable(null) } } @@ -70,39 +44,62 @@ public class BitmapCache { * @param callback the BitmapAvailableHandler to call on completion. * @param resourceUrl the URL to fetch */ - public void getPhoto(BitmapAvailableHandler callback, @NonNull URL resourceUrl) { - var bitmap = cache.get(resourceUrl); + fun getPhoto(callback: BitmapAvailableHandler, resourceUrl: URL) { + val bitmap = cache[resourceUrl] if (bitmap == null) { - var downloader = new BitmapDownloader((bitmap1) -> { + val downloader = BitmapDownloader({ bitmap1: Bitmap? -> if (bitmap1 != null) { - cache.put(resourceUrl, bitmap1); + cache[resourceUrl] = bitmap1 } // inform all requestors about the available image - synchronized (requests) { - var handlers = requests.remove(resourceUrl); - if (handlers == null) { - Log.wtf(TAG, "Request result without a saved requestor. This should never happen."); - } else { - handlers.forEach(handler -> handler.onBitmapAvailable(bitmap1)); - } + synchronized(requests) { + val handlers: Collection? = requests.remove(resourceUrl) + handlers?.forEach(Consumer { handler: BitmapAvailableHandler -> + handler.onBitmapAvailable( + bitmap1 + ) + }) + ?: Log.wtf( + TAG, + "Request result without a saved requestor. This should never happen." + ) } - }, resourceUrl); - synchronized (requests) { - var handlers = requests.get(resourceUrl); + }, resourceUrl) + synchronized(requests) { + var handlers = requests[resourceUrl] if (handlers == null) { - handlers = new ArrayList<>(); - handlers.add(callback); - requests.put(resourceUrl, handlers); + handlers = ArrayList() + handlers.add(callback) + requests.put(resourceUrl, handlers) } else { - handlers.add(callback); + handlers.add(callback) } } - downloader.start(); + downloader.start() } else { - callback.onBitmapAvailable(bitmap); + callback.onBitmapAvailable(bitmap) } - } -} + companion object { + /** + * The singleton. + */ + var instance: BitmapCache? = null + /** + * Get the BitmapCache instance. + * + * @return the single instance of BitmapCache. + */ + get() { + synchronized(BitmapCache::class.java) { + if (field == null) { + field = BitmapCache() + } + return field + } + } + private set + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/BitmapDownloader.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/BitmapDownloader.kt index 4ade5dd9..8afb5131 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/BitmapDownloader.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/BitmapDownloader.kt @@ -1,55 +1,46 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.util; +package de.bahnhoefe.deutschlands.bahnhofsfotos.util -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.util.Log; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Log +import java.io.IOException +import java.net.HttpURLConnection +import java.net.URL /** * Helper class to download image in background */ -public class BitmapDownloader extends Thread { - - private final String TAG = BitmapDownloader.class.getSimpleName(); - private final BitmapAvailableHandler bitmapAvailableHandler; - private final URL url; - - /** - * Construct a bitmap Downloader for the given URL - * - * @param handler the BitmapAvailableHandler instance that is called on completion - * @param url the URL to fetch the Bitmap from - */ - public BitmapDownloader(BitmapAvailableHandler handler, URL url) { - super(); - this.bitmapAvailableHandler = handler; - this.url = url; - } - - @Override - public void run() { - Bitmap bitmap = null; +class BitmapDownloader +/** + * Construct a bitmap Downloader for the given URL + * + * @param handler the BitmapAvailableHandler instance that is called on completion + * @param url the URL to fetch the Bitmap from + */(private val bitmapAvailableHandler: BitmapAvailableHandler, private val url: URL) : Thread() { + private val TAG = BitmapDownloader::class.java.simpleName + override fun run() { + var bitmap: Bitmap? = null try { - Log.i(TAG, "Fetching Bitmap from URL: " + url); - var httpConnection = (HttpURLConnection) url.openConnection(); - try (var is = httpConnection.getInputStream()) { - if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { - String contentType = httpConnection.getContentType(); + Log.i(TAG, "Fetching Bitmap from URL: $url") + val httpConnection = url.openConnection() as HttpURLConnection + httpConnection.inputStream.use { `is` -> + if (httpConnection.responseCode == HttpURLConnection.HTTP_OK) { + val contentType = httpConnection.contentType if (contentType != null && !contentType.startsWith("image")) { - Log.w(TAG, "Supplied URL does not appear to be an image resource (type=" + contentType + ")"); + Log.w( + TAG, + "Supplied URL does not appear to be an image resource (type=$contentType)" + ) } - bitmap = BitmapFactory.decodeStream(is); + bitmap = BitmapFactory.decodeStream(`is`) } else { - Log.e(TAG, "Error downloading photo: " + httpConnection.getResponseCode()); + Log.e(TAG, "Error downloading photo: " + httpConnection.responseCode) } } - } catch (IOException e) { - Log.e(TAG, "Could not download photo"); - bitmap = null; + } catch (e: IOException) { + Log.e(TAG, "Could not download photo") + bitmap = null } - bitmapAvailableHandler.onBitmapAvailable(bitmap); + bitmapAvailableHandler.onBitmapAvailable(bitmap) } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/ConnectionUtil.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/ConnectionUtil.kt index a8e2b620..1d387cac 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/ConnectionUtil.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/ConnectionUtil.kt @@ -1,27 +1,19 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.util; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.widget.Toast; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; - -public class ConnectionUtil { - - public static boolean checkInternetConnection(Context context) { - var cm = - (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); - - var activeNetwork = cm.getActiveNetworkInfo(); - var isConnected = activeNetwork != null && - activeNetwork.isConnectedOrConnecting(); - +package de.bahnhoefe.deutschlands.bahnhofsfotos.util + +import android.content.Context +import android.net.ConnectivityManager +import android.widget.Toast +import de.bahnhoefe.deutschlands.bahnhofsfotos.R + +object ConnectionUtil { + fun checkInternetConnection(context: Context): Boolean { + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val activeNetwork = cm.activeNetworkInfo + val isConnected = activeNetwork != null && + activeNetwork.isConnectedOrConnecting if (!isConnected) { - Toast.makeText(context, R.string.no_internet_connection, Toast.LENGTH_LONG).show(); + Toast.makeText(context, R.string.no_internet_connection, Toast.LENGTH_LONG).show() } - - return isConnected; + return isConnected } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/Constants.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/Constants.kt index 0e716bf8..ff6128c0 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/Constants.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/Constants.kt @@ -1,75 +1,73 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.util; +package de.bahnhoefe.deutschlands.bahnhofsfotos.util -public class Constants { - - public static final int STORED_PHOTO_WIDTH = 1920; - public static final int STORED_PHOTO_QUALITY = 95; - public static final String CURSOR_ADAPTER_ID = "_id"; +object Constants { + const val STORED_PHOTO_WIDTH = 1920 + const val STORED_PHOTO_QUALITY = 95 + const val CURSOR_ADAPTER_ID = "_id" /** * Columns of Stations table */ - public final static class STATIONS { - public static final String ROWID = "rowid"; - public static final String ID = "id"; - public static final String TITLE = "title"; - public static final String NORMALIZED_TITLE = "normalizedTitle"; - public static final String COUNTRY = "country"; - public static final String LAT = "lat"; - public static final String LON = "lon"; - public static final String PHOTO_ID = "photoId"; - public static final String PHOTO_URL = "photoUrl"; - public static final String PHOTOGRAPHER = "photographer"; - public static final String PHOTOGRAPHER_URL = "photographerUrl"; - public static final String LICENSE = "license"; - public static final String LICENSE_URL = "licenseUrl"; - public static final String DS100 = "DS100"; - public static final String ACTIVE = "active"; - public static final String OUTDATED = "outdated"; + object STATIONS { + const val ROWID = "rowid" + const val ID = "id" + const val TITLE = "title" + const val NORMALIZED_TITLE = "normalizedTitle" + const val COUNTRY = "country" + const val LAT = "lat" + const val LON = "lon" + const val PHOTO_ID = "photoId" + const val PHOTO_URL = "photoUrl" + const val PHOTOGRAPHER = "photographer" + const val PHOTOGRAPHER_URL = "photographerUrl" + const val LICENSE = "license" + const val LICENSE_URL = "licenseUrl" + const val DS100 = "DS100" + const val ACTIVE = "active" + const val OUTDATED = "outdated" } /** * Columns of Countries table */ - public final static class COUNTRIES { - public static final String COUNTRYNAME = "country"; - public static final String COUNTRYSHORTCODE = "countryflag"; - public static final String EMAIL = "mail"; - public static final String ROWID_COUNTRIES = "rowidcountries"; - public static final String TIMETABLE_URL_TEMPLATE = "timetable_url_template"; - public static final String OVERRIDE_LICENSE = "override_license"; + object COUNTRIES { + const val COUNTRYNAME = "country" + const val COUNTRYSHORTCODE = "countryflag" + const val EMAIL = "mail" + const val ROWID_COUNTRIES = "rowidcountries" + const val TIMETABLE_URL_TEMPLATE = "timetable_url_template" + const val OVERRIDE_LICENSE = "override_license" } /** * Columns of ProviderApps table */ - public final static class PROVIDER_APPS { - public static final String COUNTRYSHORTCODE = "countryflag"; - public static final String PA_TYPE = "type"; - public static final String PA_NAME = "name"; - public static final String PA_URL = "url"; + object PROVIDER_APPS { + const val COUNTRYSHORTCODE = "countryflag" + const val PA_TYPE = "type" + const val PA_NAME = "name" + const val PA_URL = "url" } /** * Columns of Uploads table */ - public final static class UPLOADS { - public static final String ID = "id"; - public static final String REMOTE_ID = "remoteId"; - public static final String TITLE = "title"; - public static final String COUNTRY = "country"; - public static final String STATION_ID = "stationId"; - public static final String LAT = "lat"; - public static final String LON = "lon"; - public static final String PROBLEM_TYPE = "problemType"; - public static final String INBOX_URL = "inboxUrl"; - public static final String UPLOAD_STATE = "uploadState"; - public static final String REJECTED_REASON = "rejectedReason"; - public static final String CREATED_AT = "createdAt"; - public static final String COMMENT = "comment"; - public static final String JOIN_STATION_TITLE = "stationTitle"; // only for join with station - public static final String ACTIVE = "active"; - public static final String CRC32 = "crc32"; + object UPLOADS { + const val ID = "id" + const val REMOTE_ID = "remoteId" + const val TITLE = "title" + const val COUNTRY = "country" + const val STATION_ID = "stationId" + const val LAT = "lat" + const val LON = "lon" + const val PROBLEM_TYPE = "problemType" + const val INBOX_URL = "inboxUrl" + const val UPLOAD_STATE = "uploadState" + const val REJECTED_REASON = "rejectedReason" + const val CREATED_AT = "createdAt" + const val COMMENT = "comment" + const val JOIN_STATION_TITLE = "stationTitle" // only for join with station + const val ACTIVE = "active" + const val CRC32 = "crc32" } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/ExceptionHandler.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/ExceptionHandler.kt index 08809cf3..08103b7f 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/ExceptionHandler.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/ExceptionHandler.kt @@ -1,78 +1,66 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.util; +package de.bahnhoefe.deutschlands.bahnhofsfotos.util -import android.content.Context; -import android.content.Intent; -import android.os.Build; +import android.content.Context +import android.content.Intent +import android.os.Build +import de.bahnhoefe.deutschlands.bahnhofsfotos.BuildConfig +import de.bahnhoefe.deutschlands.bahnhofsfotos.ShowErrorActivity +import java.io.PrintWriter +import java.io.StringWriter -import androidx.annotation.NonNull; - -import java.io.PrintWriter; -import java.io.StringWriter; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.BuildConfig; -import de.bahnhoefe.deutschlands.bahnhofsfotos.ShowErrorActivity; - -public class ExceptionHandler implements Thread.UncaughtExceptionHandler { - - private final Context context; - private final Thread.UncaughtExceptionHandler defaultExceptionHandler; - - public ExceptionHandler(Context context, Thread.UncaughtExceptionHandler defaultExceptionHandler) { - this.context = context; - this.defaultExceptionHandler = defaultExceptionHandler; - } - - @Override - public void uncaughtException(@NonNull Thread thread, @NonNull Throwable exception) { +class ExceptionHandler( + private val context: Context, + private val defaultExceptionHandler: Thread.UncaughtExceptionHandler +) : Thread.UncaughtExceptionHandler { + override fun uncaughtException(thread: Thread, exception: Throwable) { try { - var errorReport = generateErrorReport(formatException(thread, exception)); - var intent = new Intent(context, ShowErrorActivity.class); - intent.putExtra(ShowErrorActivity.EXTRA_ERROR_TEXT, errorReport); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); + val errorReport = generateErrorReport(formatException(thread, exception)) + val intent = Intent(context, ShowErrorActivity::class.java) + intent.putExtra(ShowErrorActivity.Companion.EXTRA_ERROR_TEXT, errorReport) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + context.startActivity(intent) // Pass exception to OS for graceful handling - OS will report it via ADB // and close all activities and services. - defaultExceptionHandler.uncaughtException(thread, exception); - } catch (Exception fatalException) { + defaultExceptionHandler.uncaughtException(thread, exception) + } catch (fatalException: Exception) { // do not recurse into custom handler if exception is thrown during // exception handling. Pass this ultimate fatal exception to OS - defaultExceptionHandler.uncaughtException(thread, fatalException); + defaultExceptionHandler.uncaughtException(thread, fatalException) } } - private String formatException(Thread thread, Throwable exception) { - var stringBuilder = new StringBuilder(); - stringBuilder.append(String.format("Exception in thread \"%s\": ", thread.getName())); + private fun formatException(thread: Thread, exception: Throwable): String { + val stringBuilder = StringBuilder() + stringBuilder.append(String.format("Exception in thread \"%s\": ", thread.name)) // print available stacktrace - var writer = new StringWriter(); - exception.printStackTrace(new PrintWriter(writer)); - stringBuilder.append(writer); - - return stringBuilder.toString(); + val writer = StringWriter() + exception.printStackTrace(PrintWriter(writer)) + stringBuilder.append(writer) + return stringBuilder.toString() } - private String generateErrorReport(String stackTrace) { - return "### App information\n" + - "* ID: " + BuildConfig.APPLICATION_ID + "\n" + - "* Version: " + BuildConfig.VERSION_CODE + " " + BuildConfig.VERSION_NAME + "\n" + - "\n" + - "### Device information\n" + - "* Brand: " + Build.BRAND + "\n" + - "* Device: " + Build.DEVICE + "\n" + - "* Model: " + Build.MODEL + "\n" + - "* Id: " + Build.ID + "\n" + - "* Product: " + Build.PRODUCT + "\n" + - "\n" + - "### Firmware\n" + - "* SDK: " + Build.VERSION.SDK_INT + "\n" + - "* Release: " + Build.VERSION.RELEASE + "\n" + - "* Incremental: " + Build.VERSION.INCREMENTAL + "\n" + - "\n" + - "### Cause of error\n" + - "```java\n" + - stackTrace + "\n" + - "```\n"; - } + private fun generateErrorReport(stackTrace: String): String { + return """### App information +* ID: ${BuildConfig.APPLICATION_ID} +* Version: ${BuildConfig.VERSION_CODE} ${BuildConfig.VERSION_NAME} -} +### Device information +* Brand: ${Build.BRAND} +* Device: ${Build.DEVICE} +* Model: ${Build.MODEL} +* Id: ${Build.ID} +* Product: ${Build.PRODUCT} + +### Firmware +* SDK: ${Build.VERSION.SDK_INT} +* Release: ${Build.VERSION.RELEASE} +* Incremental: ${Build.VERSION.INCREMENTAL} + +### Cause of error +```java +$stackTrace +``` +""" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/FileUtils.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/FileUtils.kt index f5e49103..d32e3095 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/FileUtils.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/FileUtils.kt @@ -1,39 +1,34 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.util; +package de.bahnhoefe.deutschlands.bahnhofsfotos.util -import android.content.Context; -import android.os.Environment; -import android.util.Log; +import android.content.Context +import android.os.Environment +import android.util.Log +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.util.Locale +import java.util.Objects -import androidx.annotation.Nullable; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.Locale; -import java.util.Objects; - -public class FileUtils { - - private static final String TAG = FileUtils.class.getSimpleName(); +object FileUtils { + private val TAG = FileUtils::class.java.simpleName /** * Get the base directory for storing fotos * * @return the File denoting the base directory or null, if cannot write to it */ - @Nullable - public static File getLocalFotoDir(Context context) { - return mkdirs(Objects.requireNonNull(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES))); + fun getLocalFotoDir(context: Context): File? { + return mkdirs(Objects.requireNonNull(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES))) } - private static File mkdirs(File dir) { + private fun mkdirs(dir: File): File? { try { - Files.createDirectories(dir.toPath()); - return dir; - } catch (IOException e) { - Log.e(TAG, "Cannot create directory structure " + dir.getAbsolutePath(), e); + Files.createDirectories(dir.toPath()) + return dir + } catch (e: IOException) { + Log.e(TAG, "Cannot create directory structure " + dir.absolutePath, e) } - return null; + return null } /** @@ -41,30 +36,26 @@ public class FileUtils { * * @return the File */ - @Nullable - public static File getStoredMediaFile(Context context, Long uploadId) { - var mediaStorageDir = FileUtils.getLocalFotoDir(context); - if (mediaStorageDir == null) { - return null; - } - - var storeMediaFile = new File(mediaStorageDir, String.format(Locale.ENGLISH, "%d.jpg", uploadId)); - Log.d(TAG, "StoredMediaFile: " + storeMediaFile); - - return storeMediaFile; + fun getStoredMediaFile(context: Context, uploadId: Long?): File? { + val mediaStorageDir = getLocalFotoDir(context) + ?: return null + val storeMediaFile = + File(mediaStorageDir, String.format(Locale.ENGLISH, "%d.jpg", uploadId)) + Log.d(TAG, "StoredMediaFile: $storeMediaFile") + return storeMediaFile } - public static File getImageCacheFile(Context applicationContext, String imageId) { - File imagePath = new File(applicationContext.getCacheDir(), "images"); - mkdirs(imagePath); - return new File(imagePath, imageId + ".jpg"); + fun getImageCacheFile(applicationContext: Context, imageId: String): File { + val imagePath = File(applicationContext.cacheDir, "images") + mkdirs(imagePath) + return File(imagePath, "$imageId.jpg") } - public static void deleteQuietly(File file) { + fun deleteQuietly(file: File?) { try { - Files.delete(file.toPath()); - } catch (IOException exception) { - Log.w(TAG, "unable to delete file " + file, exception); + Files.delete(file!!.toPath()) + } catch (exception: IOException) { + Log.w(TAG, "unable to delete file $file", exception) } } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/KeyValueSpinnerItem.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/KeyValueSpinnerItem.kt index c9803fdc..57576173 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/KeyValueSpinnerItem.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/KeyValueSpinnerItem.kt @@ -1,29 +1,8 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.util; +package de.bahnhoefe.deutschlands.bahnhofsfotos.util -import androidx.annotation.NonNull; +class KeyValueSpinnerItem(val spinnerText: String, val value: String) { -public class KeyValueSpinnerItem { - - private final String spinnerText; - private final String value; - - public KeyValueSpinnerItem(String spinnerText, String value) { - this.spinnerText = spinnerText; - this.value = value; + override fun toString(): String { + return spinnerText } - - public String getSpinnerText() { - return spinnerText; - } - - public String getValue() { - return value; - } - - @Override - @NonNull - public String toString() { - return spinnerText; - } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/NavItem.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/NavItem.kt index 4c08e901..496de0d1 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/NavItem.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/NavItem.kt @@ -1,51 +1,59 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.util; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.MapsActivity; -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; - -public enum NavItem { - - OEPNV(R.string.nav_oepnv, R.drawable.ic_directions_bus_gray_24px, "google.navigation:ll=%s,%s&mode=Transit"), - CAR(R.string.nav_car, R.drawable.ic_directions_car_gray_24px, "google.navigation:ll=%s,%s&mode=d"), - BIKE(R.string.nav_bike, R.drawable.ic_directions_bike_gray_24px, "google.navigation:ll=%s,%s&mode=b"), - WALK(R.string.nav_walk, R.drawable.ic_directions_walk_gray_24px, "google.navigation:ll=%s,%s&mode=w"), - SHOW(R.string.nav_show, R.drawable.ic_info_gray_24px, "geo:0,0?q=%s,%s(%s)"), +package de.bahnhoefe.deutschlands.bahnhofsfotos.util + +import android.content.Context +import android.content.Intent +import android.net.Uri +import de.bahnhoefe.deutschlands.bahnhofsfotos.MapsActivity +import de.bahnhoefe.deutschlands.bahnhofsfotos.R + +enum class NavItem(val textRes: Int, val iconRes: Int, private val uriTemplate: String?) { + OEPNV( + R.string.nav_oepnv, + R.drawable.ic_directions_bus_gray_24px, + "google.navigation:ll=%s,%s&mode=Transit" + ), + CAR( + R.string.nav_car, + R.drawable.ic_directions_car_gray_24px, + "google.navigation:ll=%s,%s&mode=d" + ), + BIKE( + R.string.nav_bike, + R.drawable.ic_directions_bike_gray_24px, + "google.navigation:ll=%s,%s&mode=b" + ), + WALK( + R.string.nav_walk, + R.drawable.ic_directions_walk_gray_24px, + "google.navigation:ll=%s,%s&mode=w" + ), + SHOW( + R.string.nav_show, R.drawable.ic_info_gray_24px, "geo:0,0?q=%s,%s(%s)" + ), SHOW_ON_MAP(R.string.nav_show_on_map, R.drawable.ic_map_gray_24px, null) { - @Override - public Intent createIntent(Context packageContext, double lat, double lon, String text, int markerRes) { - var intent = new Intent(packageContext, MapsActivity.class); - intent.putExtra(MapsActivity.EXTRAS_LATITUDE, lat); - intent.putExtra(MapsActivity.EXTRAS_LONGITUDE, lon); - intent.putExtra(MapsActivity.EXTRAS_MARKER, markerRes); - return intent; + override fun createIntent( + packageContext: Context?, + lat: Double, + lon: Double, + text: String?, + markerRes: Int + ): Intent { + val intent = Intent(packageContext, MapsActivity::class.java) + intent.putExtra(MapsActivity.Companion.EXTRAS_LATITUDE, lat) + intent.putExtra(MapsActivity.Companion.EXTRAS_LONGITUDE, lon) + intent.putExtra(MapsActivity.Companion.EXTRAS_MARKER, markerRes) + return intent } }; - private final int textRes; - private final int iconRes; - private final String uriTemplate; - - NavItem(int textRes, int iconRes, String uriTemplate) { - this.textRes = textRes; - this.iconRes = iconRes; - this.uriTemplate = uriTemplate; - } - - public int getTextRes() { - return textRes; - } - - public int getIconRes() { - return iconRes; + open fun createIntent( + packageContext: Context?, + lat: Double, + lon: Double, + text: String?, + markerRes: Int + ): Intent { + val uriString = String.format(uriTemplate!!, lat, lon, text) + return Intent(Intent.ACTION_VIEW, Uri.parse(uriString)) } - - public Intent createIntent(Context packageContext, double lat, double lon, String text, int markerRes) { - var uriString = String.format(uriTemplate, lat, lon, text); - return new Intent(Intent.ACTION_VIEW, Uri.parse(uriString)); - } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/PKCEUtil.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/PKCEUtil.kt index 5b6d4d06..d6c4691a 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/PKCEUtil.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/PKCEUtil.kt @@ -1,37 +1,35 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.util; - -import java.nio.charset.Charset; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Base64; - -public class PKCEUtil { - - private String verifier = ""; - - public String getCodeVerifier() { - return verifier; +package de.bahnhoefe.deutschlands.bahnhofsfotos.util + +import java.nio.charset.Charset +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.security.SecureRandom +import java.util.Base64 + +class PKCEUtil { + var codeVerifier = "" + private set + + @get:Throws(NoSuchAlgorithmException::class) + val codeChallenge: String + get() { + codeVerifier = generateCodeVerifier() + return generateCodeChallenge(codeVerifier) + } + + private fun generateCodeVerifier(): String { + val secureRandom = SecureRandom() + val codeVerifier = ByteArray(32) + secureRandom.nextBytes(codeVerifier) + return Base64.getUrlEncoder().withoutPadding().encodeToString(codeVerifier) } - public String getCodeChallenge() throws NoSuchAlgorithmException { - verifier = generateCodeVerifier(); - return generateCodeChallenge(verifier); + @Throws(NoSuchAlgorithmException::class) + private fun generateCodeChallenge(codeVerifier: String): String { + val bytes = codeVerifier.toByteArray(Charset.defaultCharset()) + val messageDigest = MessageDigest.getInstance("SHA-256") + messageDigest.update(bytes, 0, bytes.size) + val digest = messageDigest.digest() + return Base64.getUrlEncoder().withoutPadding().encodeToString(digest) } - - private String generateCodeVerifier() { - var secureRandom = new SecureRandom(); - var codeVerifier = new byte[32]; - secureRandom.nextBytes(codeVerifier); - return Base64.getUrlEncoder().withoutPadding().encodeToString(codeVerifier); - } - - private String generateCodeChallenge(String codeVerifier) throws NoSuchAlgorithmException { - var bytes = codeVerifier.getBytes(Charset.defaultCharset()); - var messageDigest = MessageDigest.getInstance("SHA-256"); - messageDigest.update(bytes, 0, bytes.length); - var digest = messageDigest.digest(); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/StationFilter.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/StationFilter.kt index 808d52ad..10884b5d 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/StationFilter.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/StationFilter.kt @@ -1,87 +1,48 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.util; +package de.bahnhoefe.deutschlands.bahnhofsfotos.util -import android.content.Context; +import android.content.Context +import de.bahnhoefe.deutschlands.bahnhofsfotos.R -import de.bahnhoefe.deutschlands.bahnhofsfotos.R; +class StationFilter(private var photo: Boolean?, var isActive: Boolean?, var nickname: String?) { -public class StationFilter { - - private Boolean photo; - - private Boolean active; - - private String nickname; - - public StationFilter(Boolean photo, Boolean active, String nickname) { - this.photo = photo; - this.active = active; - this.nickname = nickname; - } - - public Boolean hasPhoto() { - return photo; - } - - public void setPhoto(Boolean photo) { - this.photo = photo; + fun hasPhoto(): Boolean? { + return photo } - public Boolean isActive() { - return active; + fun setPhoto(photo: Boolean?) { + this.photo = photo } - public void setActive(Boolean active) { - this.active = active; - } - - public String getNickname() { - return nickname; - } - - public void setNickname(String nickname) { - this.nickname = nickname; - } - - public int getPhotoIcon() { - if (photo == null) { - return R.drawable.ic_photo_inactive_24px; - } else if (photo) { - return R.drawable.ic_photo_active_24px; + val photoIcon: Int + get() { + if (photo == null) { + return R.drawable.ic_photo_inactive_24px + } else if (photo) { + return R.drawable.ic_photo_active_24px + } + return R.drawable.ic_photo_missing_active_24px } - return R.drawable.ic_photo_missing_active_24px; - } - - public int getNicknameIcon() { - return nickname == null ? R.drawable.ic_person_inactive_24px : R.drawable.ic_person_active_24px; - } - - public int getActiveIcon() { - if (active == null) { - return R.drawable.ic_station_active_inactive_24px; - } else if (active) { - return R.drawable.ic_station_active_active_24px; + val nicknameIcon: Int + get() = if (nickname == null) R.drawable.ic_person_inactive_24px else R.drawable.ic_person_active_24px + val activeIcon: Int + get() { + if (isActive == null) { + return R.drawable.ic_station_active_inactive_24px + } else if (isActive!!) { + return R.drawable.ic_station_active_active_24px + } + return R.drawable.ic_station_inactive_active_24px } - return R.drawable.ic_station_inactive_active_24px; - } - - public int getActiveText() { - return active == null ? R.string.no_text : active ? R.string.filter_active : R.string.filter_inactive; - } - - public boolean isPhotoFilterActive() { - return photo != null; - } - - public boolean isActiveFilterActive() { - return active != null; - } - - public boolean isNicknameFilterActive() { - return nickname != null; - } - - public String getNicknameText(Context context) { - return isNicknameFilterActive() ? nickname : context.getString(R.string.no_text); - } - -} + val activeText: Int + get() = if (isActive == null) R.string.no_text else if (isActive!!) R.string.filter_active else R.string.filter_inactive + val isPhotoFilterActive: Boolean + get() = photo != null + val isActiveFilterActive: Boolean + get() = isActive != null + val isNicknameFilterActive: Boolean + get() = nickname != null + + fun getNicknameText(context: Context?): String? { + return if (isNicknameFilterActive) nickname else context!!.getString(R.string.no_text) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/Timetable.kt b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/Timetable.kt index 69859cc1..8dcec1d3 100644 --- a/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/Timetable.kt +++ b/app/src/main/kotlin/de/bahnhoefe/deutschlands/bahnhofsfotos/util/Timetable.kt @@ -1,35 +1,31 @@ -package de.bahnhoefe.deutschlands.bahnhofsfotos.util; +package de.bahnhoefe.deutschlands.bahnhofsfotos.util -import android.content.Intent; -import android.net.Uri; - -import androidx.annotation.Nullable; - -import org.apache.commons.lang3.StringUtils; - -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country; -import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station; - -public class Timetable { +import android.content.Intent +import android.net.Uri +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Country +import de.bahnhoefe.deutschlands.bahnhofsfotos.model.Station +import org.apache.commons.lang3.StringUtils +class Timetable { /** * Build an intent for an action to view a timetable for the station. * * @return the PendingIntent built. */ - @Nullable - public Intent createTimetableIntent(Country country, Station station) { + fun createTimetableIntent(country: Country, station: Station?): Intent? { if (!country.hasTimetableUrlTemplate()) { - return null; + return null } - - var timeTableTemplate = country.getTimetableUrlTemplate(); - timeTableTemplate = timeTableTemplate.replace("{id}", station.getId()); - timeTableTemplate = timeTableTemplate.replace("{title}", station.getTitle()); - timeTableTemplate = timeTableTemplate.replace("{DS100}", StringUtils.trimToEmpty(station.getDs100())); - - var timetableIntent = new Intent(Intent.ACTION_VIEW); - timetableIntent.setData(Uri.parse(timeTableTemplate)); - return timetableIntent; + var timeTableTemplate = country.timetableUrlTemplate + timeTableTemplate = timeTableTemplate!!.replace("{id}", station!!.id!!) + timeTableTemplate = timeTableTemplate.replace("{title}", station.title!!) + timeTableTemplate = timeTableTemplate.replace( + "{DS100}", StringUtils.trimToEmpty( + station.ds100 + ) + ) + val timetableIntent = Intent(Intent.ACTION_VIEW) + timetableIntent.data = Uri.parse(timeTableTemplate) + return timetableIntent } } \ No newline at end of file diff --git a/app/src/main/res/layout/item_station.xml b/app/src/main/res/layout/item_station.xml index 30dd6fdb..79bc4dc0 100644 --- a/app/src/main/res/layout/item_station.xml +++ b/app/src/main/res/layout/item_station.xml @@ -13,9 +13,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" - android:id="@+id/txtState" + android:id="@+id/txtStationKey" android:visibility="gone" - android:text="@string/bahnhofnr"/> + android:text="@string/bahnhofnr" /> + android:layout_toStartOf="@id/hasPhoto" /> + android:id="@+id/txtStationKey" /> + android:layout_below="@id/txtStationKey" />