From 6c66395c4c5be6fbd24c9b913d0b05e660e0de9f Mon Sep 17 00:00:00 2001 From: Hamza Rizwan Date: Mon, 30 Aug 2021 04:06:27 +0530 Subject: [PATCH] Build140 - Fixed DMS formatting issues - MVVM architecture for Coordinates and Custom Locations - Typos --- app/build.gradle | 4 +- .../adapters/settings/LocationsAdapter.kt | 6 + .../positional/database/dao/LocationDao.kt | 2 +- .../dialogs/gps/CoordinatesExpansion.kt | 75 +++---- .../positional/preferences/MainPreferences.kt | 7 +- .../app/simple/positional/ui/panels/GPS.kt | 12 +- .../positional/ui/subpanels/CustomLocation.kt | 189 ++++++++---------- .../simple/positional/util/DMSConverter.kt | 23 ++- .../viewmodel/CustomLocationViewModel.kt | 74 +++++++ .../viewmodels/viewmodel/LocationViewModel.kt | 85 +++++++- build.gradle | 7 +- 11 files changed, 311 insertions(+), 173 deletions(-) create mode 100644 app/src/main/java/app/simple/positional/viewmodels/viewmodel/CustomLocationViewModel.kt diff --git a/app/build.gradle b/app/build.gradle index 606c3c82..9472950e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,7 +50,7 @@ android { compileSdkVersion 30 - def appVersionCode = 139 + def appVersionCode = 140 def appVersionName = "positional_build_${appVersionCode}_final" defaultConfig { @@ -138,7 +138,7 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' // Kotlin - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0' // Google Services and Default android libs diff --git a/app/src/main/java/app/simple/positional/adapters/settings/LocationsAdapter.kt b/app/src/main/java/app/simple/positional/adapters/settings/LocationsAdapter.kt index 7badbfb5..f8bc9162 100644 --- a/app/src/main/java/app/simple/positional/adapters/settings/LocationsAdapter.kt +++ b/app/src/main/java/app/simple/positional/adapters/settings/LocationsAdapter.kt @@ -48,6 +48,12 @@ class LocationsAdapter : RecyclerView.Adapter() { notifyDataSetChanged() } + @SuppressLint("NotifyDataSetChanged") + fun addLocation(locations: Locations) { + this.locations.add(0, locations) + notifyItemInserted(0) + } + fun clearList() { for (i in locations.indices) { removeItem(0) diff --git a/app/src/main/java/app/simple/positional/database/dao/LocationDao.kt b/app/src/main/java/app/simple/positional/database/dao/LocationDao.kt index 9d24a917..06394dab 100644 --- a/app/src/main/java/app/simple/positional/database/dao/LocationDao.kt +++ b/app/src/main/java/app/simple/positional/database/dao/LocationDao.kt @@ -15,7 +15,7 @@ interface LocationDao { * @param location saves location details */ @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insetLocation(location: Locations) + suspend fun insertLocation(location: Locations) /** * Update location diff --git a/app/src/main/java/app/simple/positional/dialogs/gps/CoordinatesExpansion.kt b/app/src/main/java/app/simple/positional/dialogs/gps/CoordinatesExpansion.kt index 0b49e541..1791c4b8 100644 --- a/app/src/main/java/app/simple/positional/dialogs/gps/CoordinatesExpansion.kt +++ b/app/src/main/java/app/simple/positional/dialogs/gps/CoordinatesExpansion.kt @@ -1,7 +1,6 @@ package app.simple.positional.dialogs.gps import android.content.* -import android.location.Location import android.os.Bundle import android.os.Handler import android.os.Looper @@ -10,8 +9,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope -import androidx.localbroadcastmanager.content.LocalBroadcastManager import app.simple.positional.BuildConfig import app.simple.positional.R import app.simple.positional.decorations.ripple.DynamicRippleImageButton @@ -21,6 +20,7 @@ import app.simple.positional.util.DMSConverter import app.simple.positional.util.HtmlHelper.fromHtml import app.simple.positional.util.TextViewUtils.setTextAnimation import app.simple.positional.util.UTMConverter +import app.simple.positional.viewmodels.viewmodel.LocationViewModel import gov.nasa.worldwind.geom.Angle import gov.nasa.worldwind.geom.coords.MGRSCoord import kotlinx.coroutines.Dispatchers @@ -29,7 +29,7 @@ import kotlinx.coroutines.withContext class CoordinatesExpansion : CustomBottomSheetDialogFragment() { - private lateinit var broadcastReceiver: BroadcastReceiver + private lateinit var locationViewModel: LocationViewModel private val handler = Handler(Looper.getMainLooper()) private lateinit var coordinatesDataTextView: TextView @@ -65,24 +65,14 @@ class CoordinatesExpansion : CustomBottomSheetDialogFragment() { copyImageButton = view.findViewById(R.id.coordinates_copy) + locationViewModel = ViewModelProvider(requireActivity()).get(LocationViewModel::class.java) + return view } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - formatCoordinates(requireArguments().getDouble("latitude"), requireArguments().getDouble("longitude")) - - broadcastReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent) { - if (intent.action == "location") { - if (MainPreferences.isCustomCoordinate()) return - val location = intent.getParcelableExtra("location") ?: return - formatCoordinates(location.latitude, location.longitude) - } - } - } - copyImageButton.setOnClickListener { handler.removeCallbacks(textAnimationRunnable) val clipboard: ClipboardManager = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager @@ -123,20 +113,40 @@ class CoordinatesExpansion : CustomBottomSheetDialogFragment() { handler.postDelayed(textAnimationRunnable, 3000) } } - } - override fun onResume() { - super.onResume() - if (!MainPreferences.isCustomCoordinate()) { - LocalBroadcastManager.getInstance(requireContext()).registerReceiver(broadcastReceiver, IntentFilter("location")) - } - } + if (MainPreferences.isCustomCoordinate()) { + with(MainPreferences.getCoordinates()) { + formatCoordinates(this[0].toDouble(), this[1].toDouble()) + } - override fun onPause() { - super.onPause() - if (!MainPreferences.isCustomCoordinate()) { - LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(broadcastReceiver) + return } + + locationViewModel.dms.observe(viewLifecycleOwner, { + dmsLatitude.text = it.first + dmsLongitude.text = it.second + }) + + locationViewModel.dm.observe(viewLifecycleOwner, { + dmLatitude.text = it.first + dmLongitude.text = it.second + }) + + locationViewModel.dd.observe(viewLifecycleOwner, { + ddLatitude.text = it.first + ddLongitude.text = it.second + }) + + locationViewModel.mgrs.observe(viewLifecycleOwner, { + mgrsCoordinates.text = it + }) + + locationViewModel.utm.observe(viewLifecycleOwner, { + utmZone.text = fromHtml("${getString(R.string.utm_zone)} ${it.zone}") + utmEasting.text = fromHtml("${getString(R.string.utm_easting)} ${it.easting}") + utmNorthing.text = fromHtml("${getString(R.string.utm_northing)} ${it.northing}") + utmMeridian.text = fromHtml("${getString(R.string.utm_meridian)} ${it.centralMeridian}") + }) } override fun onDestroy() { @@ -147,7 +157,6 @@ class CoordinatesExpansion : CustomBottomSheetDialogFragment() { private fun formatCoordinates(latitude: Double, longitude: Double) { viewLifecycleOwner.lifecycleScope.launch { - val dmsLatitude: Spanned val dmsLongitude: Spanned val dmLatitude: Spanned @@ -163,10 +172,10 @@ class CoordinatesExpansion : CustomBottomSheetDialogFragment() { withContext(Dispatchers.Default) { dmsLatitude = fromHtml("${getString(R.string.gps_latitude)} ${DMSConverter.latitudeAsDMS(latitude, requireContext())}") dmsLongitude = fromHtml("${getString(R.string.gps_longitude)} ${DMSConverter.longitudeAsDMS(longitude, requireContext())}") - dmLatitude = fromHtml("${getString(R.string.gps_latitude)} ${DMSConverter.getLatitudeAsDM(latitude, requireContext())}") - dmLongitude = fromHtml("${getString(R.string.gps_longitude)} ${DMSConverter.getLongitudeAsDM(longitude, requireContext())}") - ddLatitude = fromHtml("${getString(R.string.gps_latitude)} ${DMSConverter.getLatitudeAsDD(latitude, requireContext())}") - ddLongitude = fromHtml("${getString(R.string.gps_longitude)} ${DMSConverter.getLongitudeAsDD(longitude, requireContext())}") + dmLatitude = fromHtml("${getString(R.string.gps_latitude)} ${DMSConverter.latitudeAsDM(latitude, requireContext())}") + dmLongitude = fromHtml("${getString(R.string.gps_longitude)} ${DMSConverter.longitudeAsDM(longitude, requireContext())}") + ddLatitude = fromHtml("${getString(R.string.gps_latitude)} ${DMSConverter.latitudeAsDD(latitude)}") + ddLongitude = fromHtml("${getString(R.string.gps_longitude)} ${DMSConverter.longitudeAsDD(longitude)}") mgrsCoord = MGRSCoord.fromLatLon(Angle.fromDegreesLatitude(latitude), Angle.fromDegreesLongitude(longitude)).toString() val utm = UTMConverter.getUTM(latitude, longitude) @@ -195,10 +204,8 @@ class CoordinatesExpansion : CustomBottomSheetDialogFragment() { } companion object { - fun newInstance(latitude: Double, longitude: Double): CoordinatesExpansion { + fun newInstance(): CoordinatesExpansion { val args = Bundle() - args.putDouble("latitude", latitude) - args.putDouble("longitude", longitude) val fragment = CoordinatesExpansion() fragment.arguments = args return fragment diff --git a/app/src/main/java/app/simple/positional/preferences/MainPreferences.kt b/app/src/main/java/app/simple/positional/preferences/MainPreferences.kt index a3eb4e25..47659116 100644 --- a/app/src/main/java/app/simple/positional/preferences/MainPreferences.kt +++ b/app/src/main/java/app/simple/positional/preferences/MainPreferences.kt @@ -1,5 +1,6 @@ package app.simple.positional.preferences +import android.annotation.SuppressLint import androidx.annotation.IntRange import androidx.annotation.NonNull import androidx.appcompat.app.AppCompatDelegate @@ -134,12 +135,14 @@ object MainPreferences { //--------------------------------------------------------------------------------------------------// + @SuppressLint("ApplySharedPref") fun setLatitude(@NotNull value: Float) { - getSharedPreferences().edit().putFloat(latitude, value).apply() + getSharedPreferences().edit().putFloat(latitude, value).commit() } + @SuppressLint("ApplySharedPref") fun setLongitude(@NotNull value: Float) { - getSharedPreferences().edit().putFloat(longitude, value).apply() + getSharedPreferences().edit().putFloat(longitude, value).commit() } fun getCoordinates(): FloatArray { diff --git a/app/src/main/java/app/simple/positional/ui/panels/GPS.kt b/app/src/main/java/app/simple/positional/ui/panels/GPS.kt index 8f4fde90..86708be4 100644 --- a/app/src/main/java/app/simple/positional/ui/panels/GPS.kt +++ b/app/src/main/java/app/simple/positional/ui/panels/GPS.kt @@ -94,7 +94,6 @@ class GPS : ScopedFragment() { private lateinit var infoText: TextView private lateinit var handler: Handler - private var filter: IntentFilter = IntentFilter() private lateinit var bottomSheetInfoPanel: BottomSheetBehavior private var location: Location? = null private var backPress: OnBackPressedDispatcher? = null @@ -414,7 +413,7 @@ class GPS : ScopedFragment() { locations.address = address.text.toString() locations.date = System.currentTimeMillis() - db.locationDao()?.insetLocation(locations) + db.locationDao()?.insertLocation(locations) db.close() isLocationSaved = true @@ -497,13 +496,8 @@ class GPS : ScopedFragment() { } coordinatesBox.setOnClickListener { - if (location.isNull() && !isCustomCoordinate) { - Toast.makeText(requireContext(), R.string.location_not_available, Toast.LENGTH_SHORT).show() - } else { - val latitude = if (isCustomCoordinate) customLatitude else location!!.latitude - val longitude = if (isCustomCoordinate) customLongitude else location!!.longitude - CoordinatesExpansion.newInstance(latitude, longitude).show(childFragmentManager, "coordinates_expansion") - } + CoordinatesExpansion.newInstance() + .show(childFragmentManager, "coordinates_expansion") } maps?.setOnMapsCallbackListener(object : MapsCallbacks { diff --git a/app/src/main/java/app/simple/positional/ui/subpanels/CustomLocation.kt b/app/src/main/java/app/simple/positional/ui/subpanels/CustomLocation.kt index 6d93f402..548a9dd0 100644 --- a/app/src/main/java/app/simple/positional/ui/subpanels/CustomLocation.kt +++ b/app/src/main/java/app/simple/positional/ui/subpanels/CustomLocation.kt @@ -16,6 +16,7 @@ import android.widget.LinearLayout import android.widget.Toast import androidx.core.widget.ContentLoadingProgressBar import androidx.core.widget.doOnTextChanged +import androidx.fragment.app.viewModels import androidx.interpolator.view.animation.LinearOutSlowInInterpolator import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ItemTouchHelper @@ -27,10 +28,12 @@ import app.simple.positional.activities.fragment.ScopedFragment import app.simple.positional.activities.subactivity.WebPageViewerActivity import app.simple.positional.adapters.settings.LocationsAdapter import app.simple.positional.database.instances.LocationDatabase +import app.simple.positional.decorations.corners.DynamicCornerLinearLayout import app.simple.positional.decorations.padding.PaddingAwareLinearLayout import app.simple.positional.decorations.popup.PopupLinearLayout import app.simple.positional.decorations.ripple.DynamicRippleImageButton import app.simple.positional.model.Locations +import app.simple.positional.popups.miscellaneous.DeletePopupMenu import app.simple.positional.popups.settings.CustomLocationPopupMenu import app.simple.positional.preferences.MainPreferences import app.simple.positional.util.ConditionUtils.isZero @@ -38,9 +41,9 @@ import app.simple.positional.util.TextViewUtils.capitalizeText import app.simple.positional.util.ViewUtils import app.simple.positional.util.ViewUtils.invisible import app.simple.positional.util.ViewUtils.visible +import app.simple.positional.viewmodels.viewmodel.CustomLocationViewModel import gov.nasa.worldwind.geom.Angle.isValidLatitude import gov.nasa.worldwind.geom.Angle.isValidLongitude -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -56,6 +59,7 @@ class CustomLocation : ScopedFragment() { private lateinit var longitudeInputEditText: EditText private lateinit var inputLayoutsContainer: PaddingAwareLinearLayout + private val customLocationViewModel: CustomLocationViewModel by viewModels() private lateinit var locationsAdapter: LocationsAdapter private lateinit var itemTouchHelper: ItemTouchHelper private val handler = Handler(Looper.getMainLooper()) @@ -115,27 +119,19 @@ class CustomLocation : ScopedFragment() { } }) - viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) { - val db = Room.databaseBuilder( - requireContext(), - LocationDatabase::class.java, - "locations.db") - .fallbackToDestructiveMigration() - .build() - - val list = db.locationDao()!!.getAllLocations() - db.close() + customLocationViewModel.customLocations.observe(viewLifecycleOwner, { + locationsAdapter.setList(it) + recyclerView.adapter = locationsAdapter + }) - withContext(Dispatchers.Main) { - if (list.isEmpty()) { - art.visible(true) - } else { - art.invisible(true) - } - locationsAdapter.setList(list) - recyclerView.adapter = locationsAdapter + customLocationViewModel.artState.observe(viewLifecycleOwner, { + if (it) { + art.visible(true) + inputLayoutsContainer.animateElevation(0F) + } else { + art.invisible(true) } - } + }) options.setOnClickListener { val popup = CustomLocationPopupMenu( @@ -146,131 +142,108 @@ class CustomLocation : ScopedFragment() { when (source) { getString(R.string.save) -> { viewLifecycleOwner.lifecycleScope.launch { - kotlin.runCatching { - var list = mutableListOf() - - withContext(Dispatchers.Default) { - if (!latitudeInputEditText.text.length.isZero() || !longitudeInputEditText.text.length.isZero()) { - if (isValidLatitude(latitudeInputEditText.text.toString().toDouble()) && isValidLongitude(longitudeInputEditText.text.toString().toDouble())) { - val db = Room.databaseBuilder(requireContext(), LocationDatabase::class.java, "locations.db").fallbackToDestructiveMigration().build() + withContext(Dispatchers.Default) { + if (!latitudeInputEditText.text.length.isZero() || !longitudeInputEditText.text.length.isZero()) { + if (isValidLatitude(latitudeInputEditText.text.toString().toDouble()) && isValidLongitude(longitudeInputEditText.text.toString().toDouble())) { + kotlin.runCatching { val locations = Locations() - try { - locations.address = if (addressInputEditText.text.isNullOrEmpty()) { - "----" - } else { - addressInputEditText.text.toString().capitalizeText() - } - locations.latitude = latitudeInputEditText.text.toString().toDouble() - locations.longitude = longitudeInputEditText.text.toString().toDouble() - locations.date = System.currentTimeMillis() - db.locationDao()?.insetLocation(location = locations) - list = db.locationDao()!!.getAllLocations() - } catch (e: NumberFormatException) { - showToast(e.message!!) - } finally { - db.close() + locations.address = if (addressInputEditText.text.isNullOrEmpty()) { + "----" + } else { + addressInputEditText.text.toString().capitalizeText() + } + locations.latitude = latitudeInputEditText.text.toString().toDouble() + locations.longitude = longitudeInputEditText.text.toString().toDouble() + locations.date = System.currentTimeMillis() + + customLocationViewModel.saveLocation(locations) + + withContext(Dispatchers.Main) { + locationsAdapter.addLocation(locations) } + }.getOrElse { + showToast(getString(R.string.failed)) } } } - - if (list.isNotEmpty()) { - locationsAdapter.setList(list) - recyclerView.smoothScrollToPosition(0) - art.invisible(true) - } else { - showToast(getString(R.string.failed)) - } - }.getOrElse { - showToast(getString(R.string.failed)) } } } + getString(R.string.delete_all) -> { - viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) { - val db = Room.databaseBuilder(requireContext(), LocationDatabase::class.java, "locations.db").build() - db.locationDao()?.nukeTable() - val list = db.locationDao()!!.getAllLocations() - - withContext(Dispatchers.Main) { - if (list.isEmpty()) { - locationsAdapter.clearList() - art.visible(true) - } + val deletePopupMenu = DeletePopupMenu( + layoutInflater.inflate(R.layout.popup_delete_confirmation, + DynamicCornerLinearLayout(requireContext())), view) + + deletePopupMenu.setOnPopupCallbacksListener(object : DeletePopupMenu.Companion.PopupDeleteCallbacks { + override fun delete() { + customLocationViewModel.deleteAll() } - } + }) } + getString(R.string.set_and_save) -> { viewLifecycleOwner.lifecycleScope.launch { - kotlin.runCatching { - withContext(Dispatchers.Default) { + withContext(Dispatchers.Default) { + kotlin.runCatching { if (latitudeInputEditText.text.toString().isNotEmpty() || longitudeInputEditText.text.toString().isNotEmpty()) { if (isValidLatitude(latitudeInputEditText.text.toString().toDouble()) && isValidLongitude(longitudeInputEditText.text.toString().toDouble())) { - val db = Room.databaseBuilder(requireContext(), LocationDatabase::class.java, "locations.db").fallbackToDestructiveMigration().build() val locations = Locations() - try { - locations.address = if (addressInputEditText.text.isNullOrEmpty()) { - "----" - } else { - addressInputEditText.text.toString().capitalizeText() - } - locations.latitude = latitudeInputEditText.text.toString().toDouble() - locations.longitude = longitudeInputEditText.text.toString().toDouble() - locations.date = System.currentTimeMillis() - db.locationDao()?.insetLocation(location = locations) - MainPreferences.setCustomCoordinates(true) - MainPreferences.setLatitude(latitudeInputEditText.text.toString().toFloat()) - MainPreferences.setLongitude(longitudeInputEditText.text.toString().toFloat()) - MainPreferences.setAddress(addressInputEditText.text.toString()) - } catch (e: NumberFormatException) { - MainPreferences.setCustomCoordinates(false) - } finally { - db.close() + locations.address = if (addressInputEditText.text.isNullOrEmpty()) { + "----" + } else { + addressInputEditText.text.toString().capitalizeText() } + locations.latitude = latitudeInputEditText.text.toString().toDouble() + locations.longitude = longitudeInputEditText.text.toString().toDouble() + locations.date = System.currentTimeMillis() + + customLocationViewModel.saveLocation(locations) + + MainPreferences.setCustomCoordinates(true) + MainPreferences.setLatitude(latitudeInputEditText.text.toString().toFloat()) + MainPreferences.setLongitude(longitudeInputEditText.text.toString().toFloat()) + MainPreferences.setAddress(addressInputEditText.text.toString()) } } - } - if (MainPreferences.isCustomCoordinate()) { - requireActivity().finishAfterTransition() - } else { + withContext(Dispatchers.Main) { + requireActivity().finishAfterTransition() + } + }.getOrElse { + MainPreferences.setCustomCoordinates(false) showToast(getString(R.string.failed)) } - }.getOrElse { - showToast(getString(R.string.failed)) } } } + getString(R.string.set_only) -> { viewLifecycleOwner.lifecycleScope.launch { - kotlin.runCatching { - withContext(Dispatchers.Default) { + withContext(Dispatchers.Default) { + kotlin.runCatching { if (latitudeInputEditText.text.toString().isNotEmpty() || longitudeInputEditText.text.toString().isNotEmpty()) { if (isValidLatitude(latitudeInputEditText.text.toString().toDouble()) && isValidLongitude(longitudeInputEditText.text.toString().toDouble())) { - try { - MainPreferences.setCustomCoordinates(true) - MainPreferences.setLatitude(latitudeInputEditText.text.toString().toFloat()) - MainPreferences.setLongitude(longitudeInputEditText.text.toString().toFloat()) - MainPreferences.setAddress(addressInputEditText.text.toString().capitalizeText()) - } catch (e: NumberFormatException) { - MainPreferences.setCustomCoordinates(false) - } + MainPreferences.setCustomCoordinates(true) + MainPreferences.setLatitude(latitudeInputEditText.text.toString().toFloat()) + MainPreferences.setLongitude(longitudeInputEditText.text.toString().toFloat()) + MainPreferences.setAddress(addressInputEditText.text.toString().capitalizeText()) } } - } - if (MainPreferences.isCustomCoordinate()) { - requireActivity().finishAfterTransition() - } else { + withContext(Dispatchers.Main) { + requireActivity().finishAfterTransition() + } + }.getOrElse { + MainPreferences.setCustomCoordinates(false) showToast(getString(R.string.failed)) } - }.getOrElse { - showToast(getString(R.string.failed)) } } } + getString(R.string.help) -> { val intent = Intent(requireActivity(), WebPageViewerActivity::class.java) intent.putExtra("source", "Custom Coordinates Help") @@ -366,10 +339,10 @@ class CustomLocation : ScopedFragment() { } override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) { - //Remove swiped item from list and notify the RecyclerView + // Remove swiped item from list and notify the RecyclerView val p0 = locationsAdapter.removeItem(viewHolder.absoluteAdapterPosition) - CoroutineScope(Dispatchers.Default).launch { + viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) { val db = Room.databaseBuilder(requireContext(), LocationDatabase::class.java, "locations.db").build() db.locationDao()?.deleteLocation(p0) if (db.locationDao()!!.getAllLocations().isEmpty()) { diff --git a/app/src/main/java/app/simple/positional/util/DMSConverter.kt b/app/src/main/java/app/simple/positional/util/DMSConverter.kt index 44c5ea63..e4cc537f 100644 --- a/app/src/main/java/app/simple/positional/util/DMSConverter.kt +++ b/app/src/main/java/app/simple/positional/util/DMSConverter.kt @@ -4,37 +4,38 @@ import android.content.Context import android.location.Location import app.simple.positional.R import app.simple.positional.math.MathExtensions +import kotlin.math.abs object DMSConverter { fun latitudeAsDMS(latitude: Double, context: Context): String { - val direction = if (latitude > 0) context.resources.getString(R.string.north_N) else context.resources.getString(R.string.south_S) - var strLatitude = toDMS(latitude) + val direction = if (latitude < 0) context.resources.getString(R.string.south_S) else context.resources.getString(R.string.north_N) + var strLatitude = toDMS(abs(latitude)) strLatitude += " $direction" return strLatitude } fun longitudeAsDMS(longitude: Double, context: Context): String { val direction = if (longitude < 0) context.resources.getString(R.string.west_W) else context.resources.getString(R.string.east_E) - var strLongitude = toDMS(longitude) + var strLongitude = toDMS(abs(longitude)) strLongitude += " $direction" return strLongitude } - fun getLatitudeAsDD(latitude: Double, context: Context): String { + fun latitudeAsDD(latitude: Double): String { return if (latitude > 0) - "${MathExtensions.round(latitude, 3)}° ${context.resources.getString(R.string.north_N)}" + "${MathExtensions.round(latitude, 3)}°" else - "${MathExtensions.round(latitude, 3)}° ${context.resources.getString(R.string.south_S)}" + "${MathExtensions.round(latitude, 3)}°" } - fun getLongitudeAsDD(longitude: Double, context: Context): String { + fun longitudeAsDD(longitude: Double): String { return if (longitude > 0) - "${MathExtensions.round(longitude, 3)}° ${context.resources.getString(R.string.east_E)}" + "${MathExtensions.round(longitude, 3)}°" else - "${MathExtensions.round(longitude, 3)}° ${context.resources.getString(R.string.west_W)}" + "${MathExtensions.round(longitude, 3)}°" } - fun getLatitudeAsDM(lat: Double, context: Context): String { + fun latitudeAsDM(lat: Double, context: Context): String { val ddmLat = replaceDelimiters(Location.convert(lat, Location.FORMAT_MINUTES)) return if (lat >= 0.0) { "$ddmLat ${context.resources.getString(R.string.north_N)}" @@ -43,7 +44,7 @@ object DMSConverter { } } - fun getLongitudeAsDM(longitude: Double, context: Context): String { + fun longitudeAsDM(longitude: Double, context: Context): String { val ddLongitude = replaceDelimiters(Location.convert(longitude, Location.FORMAT_MINUTES)) return if (longitude >= 0.0) { "$ddLongitude ${context.resources.getString(R.string.east_E)}" diff --git a/app/src/main/java/app/simple/positional/viewmodels/viewmodel/CustomLocationViewModel.kt b/app/src/main/java/app/simple/positional/viewmodels/viewmodel/CustomLocationViewModel.kt new file mode 100644 index 00000000..9016f57d --- /dev/null +++ b/app/src/main/java/app/simple/positional/viewmodels/viewmodel/CustomLocationViewModel.kt @@ -0,0 +1,74 @@ +package app.simple.positional.viewmodels.viewmodel + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import androidx.room.Room +import app.simple.positional.database.instances.LocationDatabase +import app.simple.positional.model.Locations +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class CustomLocationViewModel(application: Application) : AndroidViewModel(application) { + + val customLocations: MutableLiveData> by lazy { + MutableLiveData>().also { + loadLocations() + } + } + + val artState = MutableLiveData() + + private fun loadLocations() { + viewModelScope.launch(Dispatchers.Default) { + val db = Room.databaseBuilder( + getApplication(), + LocationDatabase::class.java, + "locations.db") + .fallbackToDestructiveMigration() + .build() + + with(db.locationDao()!!.getAllLocations()) { + artState.postValue(this.isEmpty()) + customLocations.postValue(this) + } + + db.close() + } + } + + fun saveLocation(locations: Locations) { + viewModelScope.launch(Dispatchers.Default) { + val db = Room.databaseBuilder( + getApplication(), + LocationDatabase::class.java, + "locations.db") + .fallbackToDestructiveMigration() + .build() + + db.locationDao()?.insertLocation(locations) + + with(db.locationDao()!!.getAllLocations()) { + artState.postValue(this.isEmpty()) + } + + db.close() + } + } + + fun deleteAll() { + viewModelScope.launch(Dispatchers.Default) { + val db = Room.databaseBuilder(getApplication(), LocationDatabase::class.java, "locations.db").build() + db.locationDao()?.nukeTable() + + with(db.locationDao()!!.getAllLocations()) { + artState.postValue(this.isEmpty()) + customLocations.postValue(this) + } + + db.close() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/simple/positional/viewmodels/viewmodel/LocationViewModel.kt b/app/src/main/java/app/simple/positional/viewmodels/viewmodel/LocationViewModel.kt index b7044400..d8d37b7d 100644 --- a/app/src/main/java/app/simple/positional/viewmodels/viewmodel/LocationViewModel.kt +++ b/app/src/main/java/app/simple/positional/viewmodels/viewmodel/LocationViewModel.kt @@ -6,10 +6,25 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.location.Location -import android.util.Log +import android.text.Spanned import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope import androidx.localbroadcastmanager.content.LocalBroadcastManager +import app.simple.positional.R +import app.simple.positional.util.DMSConverter +import app.simple.positional.util.DMSConverter.latitudeAsDD +import app.simple.positional.util.DMSConverter.latitudeAsDM +import app.simple.positional.util.DMSConverter.latitudeAsDMS +import app.simple.positional.util.DMSConverter.longitudeAsDD +import app.simple.positional.util.DMSConverter.longitudeAsDM +import app.simple.positional.util.DMSConverter.longitudeAsDMS +import app.simple.positional.util.HtmlHelper.fromHtml +import app.simple.positional.util.UTMConverter +import gov.nasa.worldwind.geom.Angle +import gov.nasa.worldwind.geom.coords.MGRSCoord +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import java.util.* /** @@ -26,6 +41,12 @@ class LocationViewModel(application: Application) : AndroidViewModel(application val location = MutableLiveData() val provider = MutableLiveData() + val dms = MutableLiveData>() + val dm = MutableLiveData>() + val dd = MutableLiveData>() + val mgrs = MutableLiveData() + val utm = MutableLiveData() + init { filter.addAction("location") filter.addAction("provider") @@ -35,7 +56,14 @@ class LocationViewModel(application: Application) : AndroidViewModel(application if (intent != null) { when (intent.action) { "location" -> { - location.postValue(intent.getParcelableExtra("location")) + with(intent.getParcelableExtra("location")!!) { + location.postValue(this) + dms(this) + dm(this) + dd(this) + mgrs(this) + utm(this) + } } "provider" -> { provider.postValue(intent.getStringExtra("location_provider") @@ -49,6 +77,59 @@ class LocationViewModel(application: Application) : AndroidViewModel(application LocalBroadcastManager.getInstance(getApplication()).registerReceiver(locationBroadcastReceiver, filter) } + private fun dms(location: Location) { + viewModelScope.launch(Dispatchers.Default) { + with(getApplication()) { + dms.postValue(Pair( + fromHtml("${getString(R.string.gps_latitude)} ${latitudeAsDMS(location.latitude, this)}"), + fromHtml("${getString(R.string.gps_longitude)} ${longitudeAsDMS(location.longitude, this)}") + )) + } + } + } + + private fun dm(location: Location) { + viewModelScope.launch(Dispatchers.Default) { + with(getApplication()) { + dm.postValue(Pair( + fromHtml("${getString(R.string.gps_latitude)} ${latitudeAsDM(location.latitude, this)}"), + fromHtml("${getString(R.string.gps_longitude)} ${longitudeAsDM(location.longitude, this)}") + )) + } + } + } + + private fun dd(location: Location) { + viewModelScope.launch(Dispatchers.Default) { + with(getApplication()) { + dd.postValue(Pair( + fromHtml("${getString(R.string.gps_latitude)} ${latitudeAsDD(location.latitude)}"), + fromHtml("${getString(R.string.gps_longitude)} ${longitudeAsDD(location.longitude)}") + )) + } + } + } + + private fun mgrs(location: Location) { + viewModelScope.launch(Dispatchers.Default) { + with(location) { + mgrs.postValue( + MGRSCoord.fromLatLon( + Angle.fromDegreesLatitude(latitude), + Angle.fromDegreesLongitude(longitude) + ).toString()) + } + } + } + + private fun utm(location: Location) { + viewModelScope.launch(Dispatchers.Default) { + with(location) { + utm.postValue(UTMConverter.getUTM(latitude, longitude)) + } + } + } + override fun onCleared() { super.onCleared() LocalBroadcastManager.getInstance(getApplication()).unregisterReceiver(locationBroadcastReceiver) diff --git a/build.gradle b/build.gradle index c3dc8047..5cd5d787 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.5.21' repositories { google() jcenter() maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:7.0.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21" - classpath 'com.google.gms:google-services:4.3.9' + classpath 'com.android.tools.build:gradle:7.0.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30" + classpath 'com.google.gms:google-services:4.3.10' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files