Author: Adam Evyčędo <git@apiote.xyz>
move strings to constants
%!v(PANIC=String method: strings: negative Repeat count)
diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/Api.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/Api.kt index 0d66dc74963b07142e773eb533bda2804a66e12a..73e6f5d9ede7a37a18a59ca311c10fc7f643b564 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/api/Api.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/Api.kt @@ -32,13 +32,18 @@ val feeds: FeedsSettings, val apiPath: String ) { companion object { + const val DEFAULT = "bimba.apiote.xyz" + const val HOST_KEY = "host" + const val TOKEN_KEY = "token" + const val API_PATH_KEY = "apiPath" + fun get(context: Context): Server { val preferences = context.getSharedPreferences("shp", MODE_PRIVATE) - val apiPath = preferences.getString("apiPath", "")!! + val apiPath = preferences.getString(API_PATH_KEY, "")!! val feeds = FeedsSettings.load(context, apiPath) - val host = preferences.getString("host", "bimba.apiote.xyz")!! + val host = preferences.getString(HOST_KEY, DEFAULT)!! return Server( - host, preferences.getString("token", "")!!, + host, preferences.getString(TOKEN_KEY, "")!!, feeds, apiPath ) } diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/MainActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/MainActivity.kt index 5b7701049711808d7606ecf398e3d60a65f685aa..ac05a4944b6d577160c3793997583c9c7126ccf1 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/MainActivity.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/MainActivity.kt @@ -39,7 +39,9 @@ import xyz.apiote.bimba.czwek.dashboard.ui.home.HomeFragment import xyz.apiote.bimba.czwek.dashboard.ui.map.MapFragment import xyz.apiote.bimba.czwek.dashboard.ui.voyage.VoyageFragment import xyz.apiote.bimba.czwek.databinding.ActivityMainBinding +import xyz.apiote.bimba.czwek.onboarding.FirstRunActivity import xyz.apiote.bimba.czwek.search.ResultsActivity +import xyz.apiote.bimba.czwek.settings.DownloadCitiesWorker import xyz.apiote.bimba.czwek.settings.ServerChooserActivity import xyz.apiote.bimba.czwek.settings.SettingsActivity import xyz.apiote.bimba.czwek.settings.feeds.FeedChooserActivity @@ -58,9 +60,7 @@ super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - getSharedPreferences("shp", MODE_PRIVATE).edit(true) { - putBoolean("firstRun", false) - } + FirstRunActivity.setFirstRunDone(this) ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets -> val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) @@ -166,12 +166,19 @@ } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val notificationPermissionAsked = + PreferenceManager.getDefaultSharedPreferences(this).getBoolean( + NOTIFICATION_PERMISSION_ASKED, false + ) if (ActivityCompat.checkSelfPermission( this, Manifest.permission.POST_NOTIFICATIONS - ) != PackageManager.PERMISSION_GRANTED && shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) + ) != PackageManager.PERMISSION_GRANTED && !notificationPermissionAsked ) { requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1) + PreferenceManager.getDefaultSharedPreferences(this).edit { + putBoolean(NOTIFICATION_PERMISSION_ASKED, true) + } } } } @@ -227,10 +234,10 @@ text.toString().trim().split(" ").first().trim(',').trim() ) ) { if (PreferenceManager.getDefaultSharedPreferences(applicationContext) - .getLong("cities_last_update", -1) < 0 + .getLong(DownloadCitiesWorker.LAST_UPDATE_KEY, -1) < 0 ) { if (!PreferenceManager.getDefaultSharedPreferences(applicationContext) - .getBoolean("no_geocoding_data_shown", false) + .getBoolean(NO_GEOCODING_DATA_SHOWN, false) ) { MaterialAlertDialogBuilder(this) .setIcon(R.drawable.geocoding) @@ -243,6 +250,9 @@ text.toString() ) } .show() + PreferenceManager.getDefaultSharedPreferences(applicationContext).edit{ + putBoolean(NO_GEOCODING_DATA_SHOWN, true) + } } } else { showResults(ResultsActivity.Mode.MODE_SHORT_CODE, text.toString()) @@ -257,13 +267,7 @@ /* todo [3.2] (ux,low) animation https://developer.android.com/guide/fragments/animate https://github.com/raheemadamboev/fab-explosion-animation-app */ - val intent = Intent(this, ResultsActivity::class.java).apply { - putExtra("mode", ResultsActivity.Mode.MODE_POSITION) - putExtra("query", query) - putExtra("lat", centerLatitude) - putExtra("lon", centerLongitude) - } - startActivity(intent) + startActivity(ResultsActivity.getIntent(this, ResultsActivity.Mode.MODE_POSITION, query, centerLatitude, centerLongitude)) } private fun showResults(mode: ResultsActivity.Mode, query: String = "") { @@ -271,11 +275,7 @@ /* todo [3.2] (ux,low) animation https://developer.android.com/guide/fragments/animate https://github.com/raheemadamboev/fab-explosion-animation-app */ - val intent = Intent(this, ResultsActivity::class.java).apply { - putExtra("mode", mode) - putExtra("query", query) - } - startActivity(intent) + startActivity(ResultsActivity.getIntent(this, mode, query)) } private fun setNavbarIcons(f: Fragment) { @@ -300,5 +300,10 @@ else -> { binding.bottomNavigation.menu[1].setIcon(R.drawable.home_black) } } + } + + companion object { + const val NOTIFICATION_PERMISSION_ASKED = "notificationPermissionAsked" + const val NO_GEOCODING_DATA_SHOWN = "no_geocoding_data_shown" } } \ No newline at end of file diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/Favourites.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/Favourites.kt index 711016241320500c160eac7dd14196cd1b974411..2b1d50a476c8181b6758489d60b61fe772080d09 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/Favourites.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/Favourites.kt @@ -5,7 +5,6 @@ package xyz.apiote.bimba.czwek.dashboard.ui.home import android.content.Context -import android.content.Intent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -200,13 +199,7 @@ ) } holder.root.setOnClickListener { - val intent = Intent(context, DeparturesActivity::class.java).apply { - putExtra("code", favourite.stopCode) - putExtra("name", favourite.stopName) - putExtra("feedID", favourite.feedID) - putExtra("linesFilter", favourite.lines.toTypedArray()) - } - context.startActivity(intent) + context.startActivity(DeparturesActivity.getIntent(context, favourite.stopCode, favourite.stopName, favourite.feedID, favourite.lines.toTypedArray())) } } } diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapFragment.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapFragment.kt index 42dcc1a330de20dc80aa99bab9e5755931c00351..65339fd2b8ef9eed82359fd09bd00e3bea0bce44 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapFragment.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapFragment.kt @@ -57,6 +57,13 @@ class MapFragment : Fragment() { + companion object { + const val PREFERENCES_NAME = "shp" + const val ZOOM_KEY = "mapZoom" + const val CENTRE_LATITUDE_KEY = "mapCentreLat" + const val CENTRE_LONGITUDE_KEY = "mapCentreLon" + } + private var maybeBinding: FragmentMapBinding? = null private val binding get() = maybeBinding!! @@ -96,7 +103,7 @@ binding.map.overlays.add(RotationGestureOverlay(binding.map).apply { isEnabled = true }) locationOverlay = MyLocationNewOverlay(GpsMyLocationProvider(context), binding.map) context?.let { - centreMap(it.getSharedPreferences("shp", MODE_PRIVATE)) + centreMap(it.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE)) locationOverlay.setDirectionIcon( AppCompatResources.getDrawable(it, R.drawable.navigation_arrow)?.mutate() @@ -250,10 +257,10 @@ } private fun centreMap(preferences: SharedPreferences) { maybeBinding?.map?.controller?.apply { - setZoom(preferences.getFloat("mapZoom", 17.0f).toDouble()) + setZoom(preferences.getFloat(ZOOM_KEY, 17.0f).toDouble()) val startPoint = GeoPoint( - preferences.getFloat("mapCentreLat", 52.39511f).toDouble(), - preferences.getFloat("mapCentreLon", 16.89506f).toDouble() + preferences.getFloat(CENTRE_LATITUDE_KEY, 52.39511f).toDouble(), + preferences.getFloat(CENTRE_LONGITUDE_KEY, 16.89506f).toDouble() ) setCenter(startPoint) } @@ -264,7 +271,7 @@ super.onResume() binding.map.onResume() locationOverlay.enableMyLocation() context?.let { ctx -> - ctx.getSharedPreferences("shp", MODE_PRIVATE).let { + ctx.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE).let { Configuration.getInstance() .load(ctx, it) centreMap(it) @@ -278,10 +285,10 @@ binding.map.onPause() locationOverlay.disableMyLocation() val centre = binding.map.mapCenter context?.let { ctx -> - ctx.getSharedPreferences("shp", MODE_PRIVATE).edit(true) { - putFloat("mapCentreLat", centre.latitude.toFloat()) - putFloat("mapCentreLon", centre.longitude.toFloat()) - putFloat("mapZoom", binding.map.zoomLevelDouble.toFloat()) + ctx.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE).edit(true) { + putFloat(CENTRE_LATITUDE_KEY, centre.latitude.toFloat()) + putFloat(CENTRE_LONGITUDE_KEY, centre.longitude.toFloat()) + putFloat(ZOOM_KEY, binding.map.zoomLevelDouble.toFloat()) } } handler.removeCallbacks(workRunnable) diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapViewModel.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapViewModel.kt index 6761cf6abca84e34c17a37b63e36ca2fe1992b9e..29ab95545f1d7ad0c79246c6a85e095b5f023af5 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapViewModel.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapViewModel.kt @@ -167,12 +167,7 @@ private fun showStop(content: View, stop: Stop) { context?.let { ctx -> content.findViewById<TextView>(R.id.stop_name).text = stop.name content.findViewById<Button>(R.id.departures_button).setOnClickListener { - val intent = Intent(ctx, DeparturesActivity::class.java).apply { - putExtra("code", stop.code) - putExtra("name", stop.name) - putExtra("feedID", stop.feedID) - } - startActivity(intent) + startActivity(DeparturesActivity.getIntent(requireContext(), stop.code, stop.name, stop.feedID!!)) } content.findViewById<Button>(R.id.navigation_button).setOnClickListener { try { diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/departures/Departures.kt b/app/src/main/java/xyz/apiote/bimba/czwek/departures/Departures.kt index a59744516071cdef554282c14ffd0a342a9b3fff..d30151cc7c7eecd37d11647c6c3ec2b41087d518 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/departures/Departures.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/departures/Departures.kt @@ -118,7 +118,7 @@ val alertDescriptions = alerts.map { it.description }.filter { it != "" } .joinToString(separator = "\n") holder?.moreButton?.setOnClickListener { MaterialAlertDialogBuilder(context!!) - .setTitle("Alerts") + .setTitle(R.string.alerts) .setPositiveButton(R.string.ok) { _, _ -> } .setMessage(alertDescriptions) .show() @@ -140,6 +140,11 @@ private var items: List, private val onClickListener: ((Departure) -> Unit) ) : RecyclerView.Adapter<ViewHolder>() { + + companion object { + const val ALERT_ITEM_ID = "alert" + } + var lastUpdate: ZonedDateTime = ZonedDateTime.of(0, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()) private set @@ -156,7 +161,7 @@ override fun getNewListSize() = newDepartures.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = (oldDepartures[oldItemPosition].departure?.ID - ?: "alert") == (newDepartures[newItemPosition].departure?.ID ?: "alert") + ?: ALERT_ITEM_ID) == (newDepartures[newItemPosition].departure?.ID ?: ALERT_ITEM_ID) override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldDeparture = oldDepartures[oldItemPosition] @@ -183,7 +188,7 @@ private var departuresPositions: MutableMap = HashMap() init { items.forEachIndexed { i, departure -> - departuresPositions[departure.departure?.ID ?: "alert"] = i + departuresPositions[departure.departure?.ID ?: ALERT_ITEM_ID] = i } } @@ -243,7 +248,7 @@ departures } val newPositions: MutableMap<String, Int> = HashMap() newDepartures.forEachIndexed { i, departure -> - newPositions[departure.departure?.ID ?: "alert"] = i + newPositions[departure.departure?.ID ?: ALERT_ITEM_ID] = i } val diff = DiffUtil.calculateDiff( DiffUtilCallback( @@ -417,7 +422,7 @@ findViewById (R.id.alerts).apply { visibility = View.VISIBLE setOnClickListener { MaterialAlertDialogBuilder(context) - .setTitle("Alerts") + .setTitle(R.string.alerts) .setPositiveButton(R.string.ok) { _, _ -> } .setMessage(departure.alerts.map { it.description }.filter { it != "" } .joinToString(separator = "\n")) diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesActivity.kt index 7cb5e9c07c7093ca8c6fada65b4d80e54bff7e30..94cd3b2272bb5374c418abd4d4eafbdac616328d 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesActivity.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesActivity.kt @@ -4,6 +4,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later package xyz.apiote.bimba.czwek.departures +import android.content.Context import android.content.Intent import android.net.ConnectivityManager import android.net.ConnectivityManager.NetworkCallback @@ -52,6 +53,35 @@ import java.time.ZoneId import java.time.ZonedDateTime class DeparturesActivity : AppCompatActivity() { + companion object { + const val CODE_PARAM = "code" + const val NAME_PARAM = "name" + const val FEED_PARAM = "feedID" + const val LINES_FILTER_PARAM = "linesFilter" + fun getIntent( + context: Context, + code: String, + name: String, + feedID: String, + ) = Intent(context, DeparturesActivity::class.java).apply { + putExtra(CODE_PARAM, code) + putExtra(NAME_PARAM, name) + putExtra(FEED_PARAM, feedID) + } + fun getIntent( + context: Context, + code: String, + name: String, + feedID: String, + lines: Array<String> + ) = Intent(context, DeparturesActivity::class.java).apply { + putExtra(CODE_PARAM, code) + putExtra(NAME_PARAM, name) + putExtra(FEED_PARAM, feedID) + putExtra(LINES_FILTER_PARAM, lines) + } + } + private var _binding: ActivityDeparturesBinding? = null private val binding get() = _binding!! @@ -231,7 +261,7 @@ this, R.drawable.filter ) ) - .setTitle("Filtered departures") + .setTitle(R.string.filtered_departures) .setMessage(R.string.filtered_stop_question) .setPositiveButton(R.string.filtered) { _, _ -> saveFavourite(viewModel.linesFilter.value!!.keys) @@ -345,6 +375,8 @@ val request = NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build() connectivityManager.registerNetworkCallback(request, networkCallback) } + + // throw Exception("test exception") } override fun onResume() { @@ -367,7 +399,7 @@ private fun getName(): String { return when (intent?.action) { Intent.ACTION_VIEW -> getString(R.string.stop_from_qr_code) - null -> intent?.extras?.getString("name") ?: "" + null -> intent?.extras?.getString(NAME_PARAM) ?: "" else -> "" } } @@ -381,12 +413,12 @@ } private fun getLines(): List<String>? { return when (intent?.action) { - null -> intent?.extras?.getStringArray("linesFilter")?.toList() + null -> intent?.extras?.getStringArray(LINES_FILTER_PARAM)?.toList() else -> null } } - private fun getCode() = intent?.extras?.getString("code") + private fun getCode() = intent?.extras?.getString(CODE_PARAM) fun getDepartures(force: Boolean = false) { binding.departuresUpdatesProgress.isIndeterminate = true @@ -489,8 +521,8 @@ } private fun saveFavourite(linesFilter: Set<String>) { val context = this - val feedID = intent.extras?.getString("feedID") - val code = intent?.extras?.getString("code") + val feedID = intent.extras?.getString(FEED_PARAM) + val code = intent?.extras?.getString(CODE_PARAM) if (feedID == null || code == null) { Toast.makeText(this, R.string.cannot_save_favourite, Toast.LENGTH_LONG).show() return diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesViewModel.kt b/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesViewModel.kt index 9d3b81505bb8907710dcf14bb6d543a1c5c1e3bf..c8ba0c68769ae2a9a4c561df995acbe21e8524ea 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesViewModel.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesViewModel.kt @@ -104,7 +104,7 @@ } } null -> { - val feedID = intent.extras?.getString("feedID") + val feedID = intent.extras?.getString(DeparturesActivity.FEED_PARAM) feeds?.get(feedID) ?: throw TrafficResponseException(41) } @@ -145,7 +145,7 @@ } } ?: throw TrafficResponseException(41) } - null -> intent?.extras?.getString("code") ?: throw TrafficResponseException(41) + null -> intent?.extras?.getString(DeparturesActivity.CODE_PARAM) ?: throw TrafficResponseException(41) else -> throw TrafficResponseException(41) } } diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/FirstRunActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/FirstRunActivity.kt index 117278a8722bd5277e13416aeb8abdeea2ea8c24..e7828395a0b090e4ce33797eb61fa5fc2c789f1d 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/FirstRunActivity.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/FirstRunActivity.kt @@ -6,12 +6,13 @@ package xyz.apiote.bimba.czwek.onboarding import android.app.NotificationChannel import android.app.NotificationManager +import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.edit import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.preference.PreferenceManager import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import xyz.apiote.bimba.czwek.R @@ -19,34 +20,22 @@ import xyz.apiote.bimba.czwek.dashboard.MainActivity import xyz.apiote.bimba.czwek.repo.migrateDB import xyz.apiote.bimba.czwek.settings.DownloadCitiesWorker import xyz.apiote.bimba.czwek.settings.feeds.migrateFeedsSettings -import java.time.Instant -import java.time.temporal.ChronoUnit class FirstRunActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() super.onCreate(savedInstanceState) - val preferences = getSharedPreferences("shp", MODE_PRIVATE) - migrateFeedsSettings(this) migrateDB(this) createNotificationChannels() - val (updatesEnabled, weekPassed) = PreferenceManager.getDefaultSharedPreferences(this).let { - arrayOf( - it.getBoolean("autoupdate_cities_list", false), - Instant.ofEpochSecond(it.getLong("cities_last_update", 0)).plus(7, ChronoUnit.DAYS) - .isBefore(Instant.now()) - ) - } - - if (updatesEnabled && weekPassed) { + if (DownloadCitiesWorker.shouldUpdate(this)) { WorkManager.getInstance(this) .enqueue(OneTimeWorkRequest.from(DownloadCitiesWorker::class.java)) } - val intent = if (preferences.getBoolean("firstRun", true)) { + val intent = if (getFirstRun(this)) { Intent(this, OnboardingActivity::class.java) } else { Intent(this, MainActivity::class.java) @@ -60,12 +49,27 @@ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val name = getString(R.string.cities_channel_name) val descriptionText = getString(R.string.cities_channel_description) val importance = NotificationManager.IMPORTANCE_LOW - val channel = NotificationChannel("cities_channel", name, importance).apply { + val channel = NotificationChannel(DownloadCitiesWorker.NOTIFICATION_CHANNEL, name, importance).apply { description = descriptionText } val notificationManager: NotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) + } + } + + companion object { + private const val PREFERENCES_NAME = "shp" + private const val FIRST_RUN_KEY = "firstRun" + + fun setFirstRunDone(context: Context) { + context.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE).edit { + putBoolean(FIRST_RUN_KEY, false) + } + } + + fun getFirstRun(context: Context): Boolean { + return context.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE).getBoolean(FIRST_RUN_KEY, true) } } } \ No newline at end of file diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/OnboardingActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/OnboardingActivity.kt index 7833875ef94ce6fe166c7f4536871d0827d49e19..f5df578827b6c4cc1cdb8844307a9ca4fa94b4dc 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/OnboardingActivity.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/OnboardingActivity.kt @@ -4,7 +4,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later package xyz.apiote.bimba.czwek.onboarding -import android.content.Intent import android.graphics.Typeface import android.os.Bundle import android.text.Spannable @@ -28,7 +27,7 @@ private val binding get() = _binding!! private val activityLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (!getSharedPreferences("shp", MODE_PRIVATE).getBoolean("firstRun", true)) { + if (!FirstRunActivity.getFirstRun(this)) { finish() } } @@ -79,9 +78,6 @@ } } private fun moveOn(simple: Boolean) { - val intent = Intent(this, ServerChooserActivity::class.java).apply { - putExtra("simple", simple) - } - activityLauncher.launch(intent) + activityLauncher.launch(ServerChooserActivity.getIntent(this, simple)) } } \ No newline at end of file diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt index 9a778552b653033536f19fbbb2deb113457e765b..b29210b31e39602eb0a83b32b219f1d55c41ee7b 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt @@ -5,6 +5,7 @@ package xyz.apiote.bimba.czwek.search import android.content.Context +import android.content.Intent import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener @@ -46,6 +47,33 @@ enum class Mode { MODE_LOCATION, MODE_SEARCH, MODE_POSITION, MODE_SHORT_CODE_LOCATION, MODE_SHORT_CODE } + companion object { + const val MODE_KEY = "mode" + const val QUERY_KEY = "query" + const val LATITUDE_KEY = "lat" + const val LONGITUDE_KEY = "lon" + fun getIntent( + context: Context, + mode: Mode, + query: String, + latitude: Double, + longitude: Double + ) = + Intent(context, ResultsActivity::class.java).apply { + putExtra(MODE_KEY, mode) + putExtra(QUERY_KEY, query) + putExtra(LATITUDE_KEY, latitude) + putExtra(LONGITUDE_KEY, longitude) + } + + fun getIntent(context: Context, mode: Mode, query: String) = + Intent(context, ResultsActivity::class.java).apply { + putExtra(MODE_KEY, mode) + putExtra(QUERY_KEY, query) + } + + } + private var _binding: ActivityResultsBinding? = null private val binding get() = _binding!! @@ -91,14 +119,15 @@ locate() } Mode.MODE_SHORT_CODE_LOCATION -> { - val query = intent.extras?.getString("query") - getString(R.string.stops_near_code, query) + val query = intent.extras?.getString(QUERY_KEY) + supportActionBar?.title = getString(R.string.stops_near_code, query) shortOLC = OpenLocationCode(query) locate() } Mode.MODE_SHORT_CODE -> { - val query = intent.extras?.getString("query") + val query = intent.extras?.getString(QUERY_KEY) + supportActionBar?.title = getString(R.string.stops_near_code, query) val split = query!!.trim().split(" ") val code = split.first().trim(',').trim() val freePart = split.drop(1).joinToString(" ") @@ -115,9 +144,9 @@ } } Mode.MODE_POSITION -> { - val query = intent.extras?.getString("query") - val lat = intent.extras?.getDouble("lat") - val lon = intent.extras?.getDouble("lon") + val query = intent.extras?.getString(QUERY_KEY) + val lat = intent.extras?.getDouble(LATITUDE_KEY) + val lon = intent.extras?.getDouble(LONGITUDE_KEY) supportActionBar?.title = getString(R.string.stops_near_code, query) getQueryablesByLocation(Location(null).apply { latitude = lat!! @@ -126,7 +155,7 @@ }, this) } Mode.MODE_SEARCH -> { - val query = intent.extras?.getString("query")!! + val query = intent.extras?.getString(QUERY_KEY)!! supportActionBar?.title = getString(R.string.results_for, query) getQueryablesByQuery(query, this) } @@ -135,10 +164,10 @@ } private fun getMode(): Mode { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - intent.extras!!.getSerializable("mode", Mode::class.java)!! + intent.extras!!.getSerializable(MODE_KEY, Mode::class.java)!! } else { @Suppress("DEPRECATION") - intent.extras!!.get("mode") as Mode + intent.extras!!.get(MODE_KEY) as Mode } } @@ -162,7 +191,10 @@ handler.postDelayed(runnable, 60 * 1000) locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) ?.let { onLocationChanged(it) } } catch (_: SecurityException) { - // this won’t happen because we don’t start this activity without location permission + Log.wtf( + "locate", + "this shouldn’t happen because we don’t start this activity without location permission" + ) } } diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/settings/DownloadCitiesWorker.kt b/app/src/main/java/xyz/apiote/bimba/czwek/settings/DownloadCitiesWorker.kt index 02de1d344e0666614ee02207fe90531cd3cb5626..6167fe4d3be5d7ae886b91b3ba9f6a8e00eaca33 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/settings/DownloadCitiesWorker.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/settings/DownloadCitiesWorker.kt @@ -1,7 +1,6 @@ package xyz.apiote.bimba.czwek.settings import android.Manifest -import android.app.NotificationManager import android.content.Context import android.content.pm.PackageManager import android.database.sqlite.SQLiteDatabase @@ -21,7 +20,9 @@ import java.io.BufferedInputStream import java.io.BufferedOutputStream import java.io.File import java.net.URL +import java.time.Instant import java.time.ZonedDateTime +import java.time.temporal.ChronoUnit import java.util.UUID import java.util.zip.ZipEntry import java.util.zip.ZipInputStream @@ -29,8 +30,31 @@ class DownloadCitiesWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) { + companion object { + const val AUTOUPDATE_KEY = "autoupdate_cities_list" + const val LAST_UPDATE_KEY = "cities_last_update" + const val NOTIFICATION_CHANNEL = "cities_channel" + const val DATABASE_NAME = "geocoding" + const val ETAG_HEADER_NAME = "ETag" + const val ETAG_KEY = "cities_etag" + const val RESULT_ZIP_FILE = "cities.zip" + const val CITIES_URL = "https://download.geonames.org/export/dump/cities15000.zip" + const val CITIES_FILE = "cities15000.txt" + fun shouldUpdate(context: Context): Boolean { + val (updatesEnabled, weekPassed) = PreferenceManager.getDefaultSharedPreferences(context) + .let { + arrayOf( + it.getBoolean(AUTOUPDATE_KEY, false), + Instant.ofEpochSecond(it.getLong(LAST_UPDATE_KEY, 0)).plus(7, ChronoUnit.DAYS) + .isBefore(Instant.now()) + ) + } + return updatesEnabled && weekPassed + } + } + override fun doWork(): Result { - val notificationBuilder = NotificationCompat.Builder(applicationContext, "cities_channel") + val notificationBuilder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL) .setSmallIcon(R.drawable.geocoding) .setContentTitle(applicationContext.getString(R.string.updating_geocoding_data)) .setContentText(applicationContext.getString(R.string.downloading_cities_list)) @@ -46,15 +70,15 @@ NotificationManagerCompat.from(applicationContext).notify(0, notificationBuilder.build()) } val db = SQLiteDatabase.openOrCreateDatabase( - applicationContext.getDatabasePath("geocoding").path, + applicationContext.getDatabasePath(DATABASE_NAME).path, null ) - val url = URL("https://download.geonames.org/export/dump/cities15000.zip") + val url = URL(CITIES_URL) val connection = url.openConnection() var length = connection.contentLength.toLong() - val connectionEtag = connection.getHeaderField("ETag") + val connectionEtag = connection.getHeaderField(ETAG_HEADER_NAME) val savedEtag = PreferenceManager.getDefaultSharedPreferences(applicationContext) - .getString("cities_etag", null) + .getString(ETAG_KEY, null) if (savedEtag != null && savedEtag == connectionEtag) { if (ActivityCompat.checkSelfPermission( applicationContext, @@ -77,7 +101,7 @@ .setInputStream(BufferedInputStream(connection.getInputStream())).get() val zipFileStream = BufferedOutputStream( File( applicationContext.noBackupFilesDir.path, - "cities.zip" + RESULT_ZIP_FILE ).outputStream() ) @@ -113,7 +137,7 @@ ) == PackageManager.PERMISSION_GRANTED ) { NotificationManagerCompat.from(applicationContext).notify(0, notificationBuilder.build()) } - val zipFile = File(applicationContext.noBackupFilesDir.path, "cities.zip") + val zipFile = File(applicationContext.noBackupFilesDir.path, RESULT_ZIP_FILE) length = zipFile.length() countingStream = BoundedInputStream.Builder().setInputStream(BufferedInputStream(zipFile.inputStream())) @@ -121,7 +145,7 @@ .get() val stream = ZipInputStream(countingStream) var entry: ZipEntry? = stream.nextEntry while (entry != null) { - if (entry.name != "cities15000.txt") { + if (entry.name != CITIES_FILE) { entry = stream.nextEntry continue } @@ -185,8 +209,8 @@ db.execSQL("alter table place_names2 rename to place_names") db.execSQL("create unique index place_names__name on place_names(name)") PreferenceManager.getDefaultSharedPreferences(applicationContext).edit { - putLong("cities_last_update", ZonedDateTime.now().toEpochSecond()) - putString("cities_etag", connectionEtag) + putLong(LAST_UPDATE_KEY, ZonedDateTime.now().toEpochSecond()) + putString(ETAG_KEY, connectionEtag) } db.close() diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/settings/ServerChooserActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/settings/ServerChooserActivity.kt index 88165f880c011dfc8b55b0e6d32ac1363d8b0f55..8eb69c2ac80b7122daaae47c52eb377f6755e1d4 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/settings/ServerChooserActivity.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/settings/ServerChooserActivity.kt @@ -4,6 +4,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later package xyz.apiote.bimba.czwek.settings +import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.graphics.Color @@ -30,15 +31,25 @@ import xyz.apiote.bimba.czwek.api.Server import xyz.apiote.bimba.czwek.api.TrafficFormatException import xyz.apiote.bimba.czwek.api.getBimba import xyz.apiote.bimba.czwek.databinding.ActivityServerChooserBinding +import xyz.apiote.bimba.czwek.onboarding.FirstRunActivity import xyz.apiote.bimba.czwek.settings.feeds.FeedChooserActivity class ServerChooserActivity : AppCompatActivity() { + companion object { + const val PARAM_SIMPLE = "simple" + const val IN_FEEDS_TRANSACTION = "inFeedsTransaction" + const val PREFERENCES_NAME = "shp" + fun getIntent(context: Context, simple: Boolean) = Intent(context, ServerChooserActivity::class.java).apply { + putExtra(PARAM_SIMPLE, simple) + } + } + private var _binding: ActivityServerChooserBinding? = null private val binding get() = _binding!! private val activityLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (!preferences.getBoolean("inFeedsTransaction", true)) { + if (!preferences.getBoolean(IN_FEEDS_TRANSACTION, true)) { finish() } } @@ -49,10 +60,10 @@ override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) - preferences = getSharedPreferences("shp", MODE_PRIVATE) + preferences = getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE) - if (intent.getBooleanExtra("simple", false)) { - setServer("bimba.apiote.xyz", "") + if (intent.getBooleanExtra(PARAM_SIMPLE, false)) { + setServer(Server.DEFAULT, "") checkServer(true) } else { _binding = ActivityServerChooserBinding.inflate(layoutInflater) @@ -65,7 +76,7 @@ windowInsets } preferences.edit(true) { - putBoolean("inFeedsTransaction", true) + putBoolean(IN_FEEDS_TRANSACTION, true) } if (preferences.getBoolean("shibboleet", false)) { @@ -78,7 +89,7 @@ binding.serverField.editText!!.addTextChangedListener { editable -> binding.button.isEnabled = !editable.isNullOrBlank() } - if (!preferences.getBoolean("firstRun", true)) { + if (!FirstRunActivity.getFirstRun(this)) { Server.get(this).let { server -> binding.serverField.editText!!.setText(server.host) binding.tokenField.editText!!.setText(server.token) @@ -93,7 +104,7 @@ binding.button.setTextColor(Color.WHITE) preferences.edit(true) { putBoolean("shibboleet", true) } - if (!preferences.getBoolean("firstRun", true)) { + if (!FirstRunActivity.getFirstRun(this)) { Server.get(this).let { server -> binding.serverField.editText!!.setText(server.host) binding.tokenField.editText!!.setText(server.token) @@ -107,7 +118,7 @@ setContentView(binding.root) preferences.edit(true) { putBoolean("shibboleet", false) } - if (!preferences.getBoolean("firstRun", true)) { + if (!FirstRunActivity.getFirstRun(this)) { Server.get(this).let { server -> binding.serverField.editText!!.setText(server.host) binding.tokenField.editText!!.setText(server.token) @@ -185,7 +196,7 @@ } } private fun moveOn(bimba: Bimba, isSimple: Boolean) { - val token = preferences.getString("token", "") + val token = preferences.getString(Server.TOKEN_KEY, "") if (bimba.isPrivate() && token == "") { showDialog(R.string.error, R.string.server_private_question, R.drawable.error_sec, null) @@ -204,20 +215,20 @@ } private fun setServer(hostname: String, token: String) { preferences.edit(true) { - putString("host", hostname) - putString("token", token) + putString(Server.HOST_KEY, hostname) + putString(Server.TOKEN_KEY, token) } } private fun updateServer(apiPath: String) { preferences.edit(true) { - putString("apiPath", apiPath) + putString(Server.API_PATH_KEY, apiPath) } } private fun runFeedsActivity() { activityLauncher.launch(Intent(this, FeedChooserActivity::class.java)) - if (intent.getBooleanExtra("simple", false)) { + if (intent.getBooleanExtra(PARAM_SIMPLE, false)) { finish() } } diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/settings/SettingsActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/settings/SettingsActivity.kt index 4a76cd5ef120f9419ff479301845c81d8240ceae..7f75a263c06670675535a7945c4367d2e62a30a1 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/settings/SettingsActivity.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/settings/SettingsActivity.kt @@ -70,7 +70,7 @@ findPreference ("download_cities_list")?.isEnabled = false } val citiesLastUpdate = PreferenceManager.getDefaultSharedPreferences(requireContext()) - .getLong("cities_last_update", -1) + .getLong(DownloadCitiesWorker.LAST_UPDATE_KEY, -1) if (citiesLastUpdate > 0) { val lastUpdateTime = DateUtils.getRelativeDateTimeString( context, diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedChooserActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedChooserActivity.kt index 09f4dd40f4c2eca59f1f092a66042214f88f14a6..3d49614ac2d04edf2269267b404c0f4c5ecc7b7e 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedChooserActivity.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedChooserActivity.kt @@ -23,12 +23,17 @@ import xyz.apiote.bimba.czwek.R import xyz.apiote.bimba.czwek.api.Server import xyz.apiote.bimba.czwek.dashboard.MainActivity import xyz.apiote.bimba.czwek.databinding.ActivityFeedChooserBinding +import xyz.apiote.bimba.czwek.onboarding.FirstRunActivity import xyz.apiote.bimba.czwek.repo.FeedInfo +import xyz.apiote.bimba.czwek.settings.ServerChooserActivity // TODO on internet connection -> getServer // TODO swipe to refresh? class FeedChooserActivity : AppCompatActivity() { + companion object { + const val PREFERENCES_NAME = "shp" + } private lateinit var viewModel: FeedsViewModel private var _binding: ActivityFeedChooserBinding? = null private val binding get() = _binding!! @@ -124,11 +129,11 @@ } private fun moveOn() { viewModel.settings.value?.save(this, Server.get(this)) - val preferences = getSharedPreferences("shp", MODE_PRIVATE) + val preferences = getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE) preferences.edit(true) { - putBoolean("inFeedsTransaction", false) + putBoolean(ServerChooserActivity.IN_FEEDS_TRANSACTION, false) } - if (preferences.getBoolean("firstRun", true)) { + if (FirstRunActivity.getFirstRun(this)) { val intent = Intent(this, MainActivity::class.java) startActivity(intent) } diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedSettings.kt b/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedSettings.kt index 632af91e8e3286638d6614460593ed54b59dcc18..31c0bcd141c540e1b70d418b402b6859ee6a9199 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedSettings.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedSettings.kt @@ -22,7 +22,7 @@ fun save(context: Context, server: Server) { val doc = KBson().dump(serializer(), this).toHexString() val feedsPreferences = - context.getSharedPreferences("feeds_settings", AppCompatActivity.MODE_PRIVATE) + context.getSharedPreferences(PREFERENCES_NAME, AppCompatActivity.MODE_PRIVATE) feedsPreferences.edit { val key = URLEncoder.encode(server.apiPath, "utf-8") putString(key, doc) @@ -30,9 +30,10 @@ } } companion object { + const val PREFERENCES_NAME = "feeds_settings" fun load(context: Context, apiPath: String = Server.get(context).apiPath): FeedsSettings { val doc = context.getSharedPreferences( - "feeds_settings", + PREFERENCES_NAME, Context.MODE_PRIVATE ).getString(URLEncoder.encode(apiPath, "utf-8"), null) return doc?.let { KBson().load(serializer(), doc.hexToByteArray()) } ?: FeedsSettings( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fc80263a37be6d8ee0697ece51f1bb3c570cc960..d603ba7d948feef9f2abd3c68ed8d1427f69df28 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -276,4 +276,6 @@ Send <string name="discard">Discard</string> <string name="send_with_comment">Send with commend</string> <string name="acra_notification_comment">Comment added to crash report</string> + <string name="filtered_departures">Filtered departures</string> + <string name="alerts">Alerts</string> </resources>