diff options
author | Iván Ávalos <avalos@disroot.org> | 2023-09-26 19:36:27 -0600 |
---|---|---|
committer | Iván Ávalos <avalos@disroot.org> | 2023-09-26 19:36:27 -0600 |
commit | 3e72d9b02e864f4f79ae494c67418031b7625659 (patch) | |
tree | 3d6d7ae3997b9372d50932e0e489dced12e5b07c | |
parent | 8765d82d3ad055945c6221e4f46bc38d903bf58d (diff) | |
parent | 3022b877d0cf9b7546c80953237c7bca5a4afa50 (diff) | |
download | etbsa-trackermap-mobile-3e72d9b02e864f4f79ae494c67418031b7625659.tar.gz etbsa-trackermap-mobile-3e72d9b02e864f4f79ae494c67418031b7625659.tar.bz2 etbsa-trackermap-mobile-3e72d9b02e864f4f79ae494c67418031b7625659.zip |
Merge branch 'main' of https://git.sr.ht/~avalos/trackermap-mobile
61 files changed, 1443 insertions, 228 deletions
diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/TrackerApp.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/TrackerApp.kt index aa92c91..1bb921d 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/TrackerApp.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/TrackerApp.kt @@ -28,6 +28,7 @@ import mx.trackermap.TrackerMap.android.units.UnitsViewModel import mx.trackermap.TrackerMap.client.apis.* import mx.trackermap.TrackerMap.client.infrastructure.SessionManager import mx.trackermap.TrackerMap.controllers.* +import mx.trackermap.TrackerMap.controllers.NetworkController import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger import org.koin.androidx.viewmodel.dsl.viewModel @@ -56,7 +57,8 @@ open class TrackerApp : Application() { factory { ReportsApi(get()) } factory { GeofencesApi(get()) } - factory { SessionController(get(), get()) } + factory { NetworkController(applicationContext) } + factory { SessionController(get(), get(), get()) } factory { UnitsController(get(), get()) } factory { GeofencesController(get()) } factory { ReportController(get(), get()) } diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt index 4915c49..2be9bb4 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/map/MapFragment.kt @@ -40,6 +40,7 @@ import mx.trackermap.TrackerMap.client.models.Geofence import mx.trackermap.TrackerMap.client.models.MapLayer import mx.trackermap.TrackerMap.client.models.Marker import mx.trackermap.TrackerMap.utils.MapCalculus +import kotlin.math.atan2 typealias SetupCallback = () -> Unit typealias MarkerCallback = (Int?) -> Unit @@ -186,11 +187,14 @@ open class MapFragment : GlobeMapFragment() { val colorReport = ContextCompat.getColor(requireContext(), R.color.colorReport) val colorLabel = ContextCompat.getColor(requireContext(), R.color.colorMarkerLabel) val colorLabelOutline = ContextCompat.getColor(requireContext(), R.color.colorMarkerLabelOutline) - val vectorWidth = context?.resources?.getDimensionPixelSize(R.dimen.report_label_width)?.toFloat() + val vectorWidth = requireContext().resources.getDimensionPixelSize(R.dimen.report_label_width).toFloat() + + val markerSize = requireContext().resources.getDimensionPixelSize(R.dimen.marker_size).toDouble() + val vertexSize = requireContext().resources.getDimensionPixelSize(R.dimen.vertex_size).toDouble() val vectorInfo = VectorInfo() - vectorInfo.setColor(colorReport) - vectorInfo.setLineWidth(vectorWidth ?: 20f) + vectorInfo.color = colorReport + vectorInfo.lineWidth = vectorWidth val labelInfo = LabelInfo() labelInfo.typeface = Typeface.DEFAULT_BOLD @@ -210,17 +214,17 @@ open class MapFragment : GlobeMapFragment() { when (i) { 0 -> getIcon(Marker.Type.REPORT_START) markers.size - 1 -> getIcon(Marker.Type.REPORT_END) - else -> getIcon(Marker.Type.REPORT_POSITION) + else -> getIconForDirection(points[i], points[i + 1]) } } else getIcon(marker.type) screenMarker.size = if (isReport) { // For reports, position, start and end, size must be different when (i) { - 0 -> Point2d(144.0, 144.0) - markers.size - 1 -> Point2d(144.0, 144.0) - else -> Point2d(82.0, 82.0) + 0 -> Point2d(markerSize, markerSize) + markers.size - 1 -> Point2d(markerSize, markerSize) + else -> Point2d(vertexSize, vertexSize) } - } else Point2d(144.0, 144.0) + } else Point2d(markerSize, markerSize) screenMarker.userObject = marker.id screenMarker.selectable = true @@ -292,11 +296,11 @@ open class MapFragment : GlobeMapFragment() { val colorFill = ContextCompat.getColor(requireContext(), R.color.colorGeofence) val colorLabel = ContextCompat.getColor(requireContext(), R.color.colorGeofenceLabel) val colorLabelOutline = ContextCompat.getColor(requireContext(), R.color.colorMarkerLabelOutline) - val vectorWidth = context?.resources?.getDimensionPixelSize(R.dimen.geofence_label_width)?.toFloat() + val vectorWidth = requireContext().resources.getDimensionPixelSize(R.dimen.geofence_label_width).toFloat() val vectorInfo = VectorInfo() - vectorInfo.setColor(colorFill) - vectorInfo.setLineWidth(vectorWidth ?: 4f) + vectorInfo.color = colorFill + vectorInfo.lineWidth = vectorWidth val labelInfo = LabelInfo() labelInfo.typeface = Typeface.DEFAULT_BOLD @@ -334,7 +338,7 @@ open class MapFragment : GlobeMapFragment() { } } } - } catch (e: SFException) {} + } catch (_: SFException) {} } } @@ -427,9 +431,22 @@ open class MapFragment : GlobeMapFragment() { } private fun getIcon(markerType: Marker.Type): Bitmap { + val markerSize = requireContext().resources.getDimensionPixelSize(R.dimen.marker_size) return ResourcesCompat.getDrawable( requireActivity().resources, MarkerTransformations.markerTypeToResourceId(markerType), - requireActivity().theme)!!.toBitmap(144, 144) + requireActivity().theme)!!.toBitmap(markerSize, markerSize) + } + + private fun getIconForDirection(a: Point2d, b: Point2d): Bitmap { + val vertexSize = requireContext().resources.getDimensionPixelSize(R.dimen.vertex_size) + val vectorX = b.x - a.x + val vectorY = b.y - a.y + val angleRad = atan2(vectorY, vectorX) + return ResourcesCompat.getDrawable( + requireActivity().resources, + MarkerTransformations.angleToResourceId(angleRad), + requireActivity().theme + )!!.toBitmap(vertexSize, vertexSize) } }
\ No newline at end of file diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/session/LoginFragment.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/session/LoginFragment.kt index 6a30789..8879349 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/session/LoginFragment.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/session/LoginFragment.kt @@ -67,7 +67,6 @@ class LoginFragment : Fragment() { getString(R.string.default_server_url) ) ?: getString(R.string.default_server_url) ) - loginViewModel.restoreSession() } override fun onStart() { @@ -100,6 +99,26 @@ class LoginFragment : Fragment() { } private fun setupObservers() { + loginViewModel.networkAvailable.observe(viewLifecycleOwner) { available -> + Log.d("LoginFragment", "available = $available, session = ${loginViewModel.hasSession}") + + binding.offlineBanner.root.visibility = + if (available == true) View.GONE else View.VISIBLE + + binding.infoLoading.root.visibility = when (available) { + null -> View.VISIBLE + true -> if (loginViewModel.hasSession) { + loginViewModel.restoreSession() + View.VISIBLE + } else View.GONE + false -> if (loginViewModel.hasSession) { + View.VISIBLE + } else { + View.GONE + } + } + } + loginViewModel.loginState.observe(viewLifecycleOwner) { result -> Log.d("LoginFragment", result.toString()) when (result) { @@ -164,4 +183,4 @@ class LoginFragment : Fragment() { const val PREFERENCE_SERVER_URL = "server_url" const val PREFERENCE_TOKEN = "token" } -}
\ No newline at end of file +} diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/session/LoginViewModel.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/session/LoginViewModel.kt index bcee2ec..434ac44 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/session/LoginViewModel.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/session/LoginViewModel.kt @@ -17,32 +17,46 @@ */ package mx.trackermap.TrackerMap.android.session +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.zhuinden.eventemitter.EventEmitter import com.zhuinden.eventemitter.EventSource import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import mx.trackermap.TrackerMap.client.models.SessionBody +import mx.trackermap.TrackerMap.controllers.NetworkController import mx.trackermap.TrackerMap.controllers.SessionController import org.koin.core.component.KoinComponent import org.koin.core.component.inject @DelicateCoroutinesApi class LoginViewModel : ViewModel(), KoinComponent { - + private val networkController: NetworkController by inject() private val sessionController: SessionController by inject() + private val _networkAvailable = MutableLiveData<Boolean?>(null) + val networkAvailable: LiveData<Boolean?> = _networkAvailable private val loginStateEmitter = EventEmitter<SessionController.LoginState>() val loginState: EventSource<SessionController.LoginState> = loginStateEmitter + val hasSession: Boolean get() = sessionController.hasSession init { viewModelScope.launch { + setupNetworkObserver() + } + viewModelScope.launch { setupLoginStateObserver() } } + private suspend fun setupNetworkObserver() { + networkController.networkAvailable.collect { + _networkAvailable.postValue(it) + } + } + private suspend fun setupLoginStateObserver() { sessionController.loginStateFlow.collect { it?.let { @@ -52,7 +66,7 @@ class LoginViewModel : ViewModel(), KoinComponent { } fun restoreSession() { - sessionController.restoreSession() + sessionController.getSession() } fun login(email: String, password: String, url: String, token: String?) { diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/shared/MarkerTransformations.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/shared/MarkerTransformations.kt index 4dd1ea7..e99b8e6 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/shared/MarkerTransformations.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/shared/MarkerTransformations.kt @@ -19,6 +19,7 @@ package mx.trackermap.TrackerMap.android.shared import mx.trackermap.TrackerMap.android.R import mx.trackermap.TrackerMap.client.models.Marker +import kotlin.math.PI object MarkerTransformations { fun markerTypeToResourceId(markerType: Marker.Type): Int { @@ -52,7 +53,7 @@ object MarkerTransformations { } } - fun markerTypeToStringId(markerType: Marker.Type): Int { + private fun markerTypeToStringId(markerType: Marker.Type): Int { return when (markerType) { Marker.Type.ANIMAL -> R.string.unit_category_animal Marker.Type.BACKHOE -> R.string.unit_category_backhoe @@ -87,4 +88,20 @@ object MarkerTransformations { fun categoryToStringId(category: String?): Int { return markerTypeToStringId(Marker.categoryToMarkerType(category)) } + + private const val STEP = PI / 8 + + @OptIn(ExperimentalStdlibApi::class) + fun angleToResourceId(rad: Double): Int = when (rad) { + in 0.0 ..< STEP -> R.drawable.angle_0 + in STEP ..< STEP * 3 -> R.drawable.angle_45 + in STEP * 3 ..< STEP * 5 -> R.drawable.angle_90 + in STEP * 5 ..< STEP * 7 -> R.drawable.angle_135 + in STEP * 7 ..< STEP * 9 -> R.drawable.angle_180 + in STEP * 9 ..< STEP * 11 -> R.drawable.angle_225 + in STEP * 11 ..< STEP * 13 -> R.drawable.angle_270 + in STEP * 13 ..< STEP * 15 -> R.drawable.angle_315 + in STEP * 15 ..< STEP * 16 -> R.drawable.angle_0 + else -> angleToResourceId(PI * 2 + rad) + } }
\ No newline at end of file diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsActivity.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsActivity.kt index 8a161c3..443a205 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsActivity.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsActivity.kt @@ -28,7 +28,6 @@ import android.view.View import android.view.inputmethod.InputMethodManager import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.TooltipCompat -import androidx.core.widget.doAfterTextChanged import androidx.fragment.app.commit import kotlinx.coroutines.DelicateCoroutinesApi import mx.trackermap.TrackerMap.android.R @@ -150,6 +149,10 @@ class UnitsActivity : AppCompatActivity() { } private fun setupObservers() { + unitsViewModel.networkAvailable.observe(this) { available -> + binding.offlineBanner.root.visibility = + if (available == true) View.GONE else View.VISIBLE + } unitsViewModel.unitsDisplayMode.observe(this) { displayMode -> binding.displayModeToggle.setImageResource( when (displayMode) { diff --git a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsViewModel.kt b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsViewModel.kt index b5ed78d..05962e9 100644 --- a/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsViewModel.kt +++ b/androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsViewModel.kt @@ -27,6 +27,7 @@ import mx.trackermap.TrackerMap.client.models.Geofence import mx.trackermap.TrackerMap.client.models.MapLayer import mx.trackermap.TrackerMap.client.models.UnitInformation import mx.trackermap.TrackerMap.controllers.GeofencesController +import mx.trackermap.TrackerMap.controllers.NetworkController import mx.trackermap.TrackerMap.controllers.UnitsController import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -48,9 +49,11 @@ class UnitsViewModel( val animated: Boolean, ) + private val networkController: NetworkController by inject() private val unitsController: UnitsController by inject() private val geofenceController: GeofencesController by inject() + private var _networkAvailable = MutableLiveData<Boolean?>() private var _searchQuery = savedStateHandle.getLiveData("searchQuery", "") private var _unitsDisplayMode = MutableLiveData(UnitsDisplayMode.MAP) private var _units = MutableLiveData<List<UnitInformation>>() @@ -61,6 +64,7 @@ class UnitsViewModel( private var _geofences = MutableLiveData<Map<Int, Geofence>>() private val _camera = MutableLiveData<Camera?>() + val networkAvailable: LiveData<Boolean?> get() = _networkAvailable val searchQuery: LiveData<String> get() = _searchQuery val unitsDisplayMode: LiveData<UnitsDisplayMode> get() = _unitsDisplayMode val units: LiveData<List<UnitInformation>> get() = _units @@ -75,6 +79,9 @@ class UnitsViewModel( Log.d("UnitsViewModel", "Initializing Units View Model") unitsController.fetchUnits(viewModelScope) viewModelScope.launch { + setupNetworkObserver() + } + viewModelScope.launch { setupUnitsObserver() } viewModelScope.launch { @@ -86,6 +93,13 @@ class UnitsViewModel( } } + private suspend fun setupNetworkObserver() { + networkController.networkAvailable.collect { available -> + Log.d("UnitsViewModel", "Collecting network state") + _networkAvailable.value = available + } + } + private suspend fun setupUnitsObserver() { (unitsController.displayedUnitsFlow as StateFlow<List<UnitInformation>>).collect { units -> Log.d("UnitsViewModel", "Collecting units") diff --git a/androidApp/src/main/res/drawable/angle_0.xml b/androidApp/src/main/res/drawable/angle_0.xml new file mode 100644 index 0000000..53957d9 --- /dev/null +++ b/androidApp/src/main/res/drawable/angle_0.xml @@ -0,0 +1,22 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="6.35" + android:viewportHeight="6.35"> + <path + android:pathData="M3.1751,6.106A2.9308,2.9308 0,0 1,0.2443 3.1752,2.9308 2.9308,0 0,1 3.1751,0.2444 2.9308,2.9308 0,0 1,6.1059 3.1752,2.9308 2.9308,0 0,1 3.1751,6.106Z" + android:strokeWidth="1.00012" + android:fillColor="#008000" + android:strokeLineCap="square"/> + <path + android:pathData="M6.3501,3.176C6.3501,1.4254 4.9249,0.0002 3.1743,0.0002c-1.7506,0 -3.1738,1.4252 -3.1738,3.1758 -0,1.7506 1.4232,3.1738 3.1738,3.1738C4.9249,6.3498 6.3501,4.9266 6.3501,3.176ZM5.8618,3.176c-0,1.4866 -1.2009,2.6855 -2.6875,2.6855 -1.4866,0 -2.6855,-1.1989 -2.6855,-2.6855 -0,-1.4866 1.1989,-2.6875 2.6855,-2.6875 1.4866,0 2.6875,1.2009 2.6875,2.6875z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeLineCap="square"/> + <path + android:pathData="M5.8618,3.1757 L2.9355,1.4857V2.5831L0.2443,2.5826c0,0 -0,0.3951 -0,0.5926 0,0.1972 0,0.5917 0,0.5917l2.6912,0.0005v1.0974z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeColor="#1a1a1a" + android:strokeLineCap="square"/> +</vector> diff --git a/androidApp/src/main/res/drawable/angle_135.xml b/androidApp/src/main/res/drawable/angle_135.xml new file mode 100644 index 0000000..cd1b88e --- /dev/null +++ b/androidApp/src/main/res/drawable/angle_135.xml @@ -0,0 +1,22 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="6.35" + android:viewportHeight="6.35"> + <path + android:pathData="M5.2479,1.1026A2.9308,2.9308 90,0 1,5.2479 5.2474,2.9308 2.9308,90 0,1 1.1032,5.2474 2.9308,2.9308 0,0 1,1.1032 1.1026,2.9308 2.9308,0 0,1 5.2479,1.1026Z" + android:strokeWidth="1.00012" + android:fillColor="#008000" + android:strokeLineCap="square"/> + <path + android:pathData="M0.9311,0.9294C-0.3068,2.1673 -0.3068,4.1828 0.9311,5.4206c1.2379,1.2379 3.252,1.2365 4.4899,-0.0014 1.2379,-1.2379 1.2379,-3.2506 0,-4.4885C4.183,-0.3071 2.1689,-0.3085 0.9311,0.9294ZM1.2763,1.2747c1.0512,-1.0512 2.7481,-1.0498 3.7993,0.0014 1.0512,1.0512 1.0512,2.7467 -0,3.7979 -1.0512,1.0512 -2.7481,1.0526 -3.7993,0.0014 -1.0512,-1.0512 -1.0512,-2.7495 0,-3.8007z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeLineCap="square"/> + <path + android:pathData="M1.2761,1.2749 L2.1503,4.5391 2.9263,3.7631 4.8289,5.6664c0,0 0.2793,-0.2793 0.419,-0.419C5.3874,5.1079 5.6663,4.829 5.6663,4.829L3.7637,2.9257 4.5396,2.1498Z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeColor="#1a1a1a" + android:strokeLineCap="square"/> +</vector> diff --git a/androidApp/src/main/res/drawable/angle_180.xml b/androidApp/src/main/res/drawable/angle_180.xml new file mode 100644 index 0000000..f3fca08 --- /dev/null +++ b/androidApp/src/main/res/drawable/angle_180.xml @@ -0,0 +1,22 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="6.35" + android:viewportHeight="6.35"> + <path + android:pathData="M3.1755,0.244A2.9308,2.9308 0,0 1,6.1063 3.1748,2.9308 2.9308,0 0,1 3.1755,6.1056 2.9308,2.9308 0,0 1,0.2447 3.1748,2.9308 2.9308,0 0,1 3.1755,0.244Z" + android:strokeWidth="1.00012" + android:fillColor="#008000" + android:strokeLineCap="square"/> + <path + android:pathData="M0.0005,3.174C0.0005,4.9246 1.4257,6.3498 3.1763,6.3498c1.7506,-0 3.1738,-1.4252 3.1738,-3.1758 0,-1.7506 -1.4232,-3.1738 -3.1738,-3.1738C1.4257,0.0002 0.0005,1.4234 0.0005,3.174ZM0.4888,3.174c0,-1.4866 1.2009,-2.6855 2.6875,-2.6855 1.4866,-0 2.6855,1.1989 2.6855,2.6855 0,1.4866 -1.1989,2.6875 -2.6855,2.6875 -1.4866,-0 -2.6875,-1.2009 -2.6875,-2.6875z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeLineCap="square"/> + <path + android:pathData="M0.4888,3.1744 L3.4151,4.8643V3.7669l2.6912,0.0005c0,0 0,-0.3951 0,-0.5926 0,-0.1972 0,-0.5917 0,-0.5917L3.4151,2.5827V1.4853Z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeColor="#1a1a1a" + android:strokeLineCap="square"/> +</vector> diff --git a/androidApp/src/main/res/drawable/angle_225.xml b/androidApp/src/main/res/drawable/angle_225.xml new file mode 100644 index 0000000..bd7c512 --- /dev/null +++ b/androidApp/src/main/res/drawable/angle_225.xml @@ -0,0 +1,22 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="6.35" + android:viewportHeight="6.35"> + <path + android:pathData="M1.1036,1.1031A2.9308,2.9308 90,0 1,5.2484 1.1031,2.9308 2.9308,90 0,1 5.2484,5.2478 2.9308,2.9308 0,0 1,1.1036 5.2478,2.9308 2.9308,0 0,1 1.1036,1.1031Z" + android:strokeWidth="1.00012" + android:fillColor="#008000" + android:strokeLineCap="square"/> + <path + android:pathData="M0.9304,5.4199C2.1682,6.6578 4.1837,6.6578 5.4216,5.4199c1.2379,-1.2379 1.2365,-3.252 -0.0014,-4.4899 -1.2379,-1.2379 -3.2506,-1.2379 -4.4885,-0C-0.3061,2.1679 -0.3075,4.1821 0.9304,5.4199ZM1.2756,5.0747c-1.0512,-1.0512 -1.0498,-2.7481 0.0014,-3.7993 1.0512,-1.0512 2.7467,-1.0512 3.7979,0 1.0512,1.0512 1.0526,2.7481 0.0014,3.7993 -1.0512,1.0512 -2.7495,1.0512 -3.8007,-0z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeLineCap="square"/> + <path + android:pathData="M1.2759,5.0749 L4.54,4.2007 3.7641,3.4247 5.6674,1.5221c0,0 -0.2793,-0.2793 -0.419,-0.419C5.1089,0.9636 4.83,0.6847 4.83,0.6847L2.9267,2.5873 2.1508,1.8114Z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeColor="#1a1a1a" + android:strokeLineCap="square"/> +</vector> diff --git a/androidApp/src/main/res/drawable/angle_270.xml b/androidApp/src/main/res/drawable/angle_270.xml new file mode 100644 index 0000000..19db1cf --- /dev/null +++ b/androidApp/src/main/res/drawable/angle_270.xml @@ -0,0 +1,22 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="6.35" + android:viewportHeight="6.35"> + <path + android:pathData="M0.2455,3.1753A2.9308,2.9308 0,0 1,3.1763 0.2445,2.9308 2.9308,0 0,1 6.107,3.1753 2.9308,2.9308 0,0 1,3.1763 6.1061,2.9308 2.9308,0 0,1 0.2455,3.1753Z" + android:strokeWidth="1.00012" + android:fillColor="#008000" + android:strokeLineCap="square"/> + <path + android:pathData="M3.1755,6.3503C4.9261,6.3503 6.3513,4.9251 6.3513,3.1745c-0,-1.7506 -1.4252,-3.1738 -3.1758,-3.1738 -1.7506,-0 -3.1738,1.4232 -3.1738,3.1738C0.0017,4.9251 1.4249,6.3503 3.1755,6.3503ZM3.1755,5.862c-1.4866,-0 -2.6855,-1.2009 -2.6855,-2.6875 -0,-1.4866 1.1989,-2.6855 2.6855,-2.6855 1.4866,-0 2.6875,1.1989 2.6875,2.6855 -0,1.4866 -1.2009,2.6875 -2.6875,2.6875z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeLineCap="square"/> + <path + android:pathData="m3.1758,5.862 l1.6899,-2.9263 -1.0974,0 0.0005,-2.6912c0,0 -0.3951,0 -0.5926,-0 -0.1972,0 -0.5917,0 -0.5917,0l-0.0005,2.6912 -1.0974,0z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeColor="#1a1a1a" + android:strokeLineCap="square"/> +</vector> diff --git a/androidApp/src/main/res/drawable/angle_315.xml b/androidApp/src/main/res/drawable/angle_315.xml new file mode 100644 index 0000000..3186db0 --- /dev/null +++ b/androidApp/src/main/res/drawable/angle_315.xml @@ -0,0 +1,22 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="6.35" + android:viewportHeight="6.35"> + <path + android:pathData="M1.1038,5.2479A2.9308,2.9308 90,0 1,1.1038 1.1031,2.9308 2.9308,0 0,1 5.2486,1.1031 2.9308,2.9308 0,0 1,5.2486 5.2479,2.9308 2.9308,90 0,1 1.1038,5.2479Z" + android:strokeWidth="1.00012" + android:fillColor="#008000" + android:strokeLineCap="square"/> + <path + android:pathData="M5.4207,5.4211C6.6586,4.1832 6.6586,2.1677 5.4207,0.9299c-1.2379,-1.2379 -3.252,-1.2365 -4.4899,0.0014 -1.2379,1.2379 -1.2379,3.2506 0,4.4885C2.1687,6.6576 4.1828,6.659 5.4207,5.4211ZM5.0754,5.0758c-1.0512,1.0512 -2.7481,1.0498 -3.7993,-0.0014 -1.0512,-1.0512 -1.0512,-2.7467 -0,-3.7979 1.0512,-1.0512 2.7481,-1.0526 3.7993,-0.0014 1.0512,1.0512 1.0512,2.7495 0,3.8007z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeLineCap="square"/> + <path + android:pathData="M5.0757,5.0756 L4.2014,1.8114 3.4255,2.5874 1.5228,0.6841c0,0 -0.2793,0.2793 -0.419,0.419C0.9644,1.2426 0.6855,1.5215 0.6855,1.5215L2.5881,3.4248 1.8121,4.2007Z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeColor="#1a1a1a" + android:strokeLineCap="square"/> +</vector> diff --git a/androidApp/src/main/res/drawable/angle_45.xml b/androidApp/src/main/res/drawable/angle_45.xml new file mode 100644 index 0000000..9d8b5a9 --- /dev/null +++ b/androidApp/src/main/res/drawable/angle_45.xml @@ -0,0 +1,22 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="6.35" + android:viewportHeight="6.35"> + <path + android:pathData="M5.2467,5.2476A2.9308,2.9308 90,0 1,1.1019 5.2476,2.9308 2.9308,0 0,1 1.1019,1.1029 2.9308,2.9308 0,0 1,5.2467 1.1029,2.9308 2.9308,90 0,1 5.2467,5.2476Z" + android:strokeWidth="1.00012" + android:fillColor="#008000" + android:strokeLineCap="square"/> + <path + android:pathData="M5.4199,0.9308C4.1821,-0.3071 2.1666,-0.3071 0.9287,0.9308c-1.2379,1.2379 -1.2365,3.252 0.0014,4.4899 1.2379,1.2379 3.2506,1.2379 4.4885,-0C6.6564,4.1828 6.6578,2.1686 5.4199,0.9308ZM5.0747,1.276c1.0512,1.0512 1.0498,2.7481 -0.0014,3.7993 -1.0512,1.0512 -2.7467,1.0512 -3.7979,0 -1.0512,-1.0512 -1.0526,-2.7481 -0.0014,-3.7993 1.0512,-1.0512 2.7495,-1.0512 3.8007,-0z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeLineCap="square"/> + <path + android:pathData="M5.0744,1.2758 L1.8103,2.1501 2.5862,2.926 0.8061,4.7067 1.2251,5.1257 1.6435,5.5441 3.4236,3.7634 4.1995,4.5393Z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeColor="#1a1a1a" + android:strokeLineCap="square"/> +</vector> diff --git a/androidApp/src/main/res/drawable/angle_90.xml b/androidApp/src/main/res/drawable/angle_90.xml new file mode 100644 index 0000000..44dae03 --- /dev/null +++ b/androidApp/src/main/res/drawable/angle_90.xml @@ -0,0 +1,22 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="6.35" + android:viewportHeight="6.35"> + <path + android:pathData="M6.1058,3.175A2.9308,2.9308 0,0 1,3.175 6.1058,2.9308 2.9308,0 0,1 0.2442,3.175 2.9308,2.9308 0,0 1,3.175 0.2442,2.9308 2.9308,0 0,1 6.1058,3.175Z" + android:strokeWidth="1.00012499" + android:fillColor="#008000" + android:strokeLineCap="square"/> + <path + android:pathData="M3.1758,0C1.4252,0 0,1.4252 0,3.1758c0,1.7506 1.4252,3.1738 3.1758,3.1738 1.7506,0 3.1738,-1.4232 3.1738,-3.1738C6.3496,1.4252 4.9264,0 3.1758,0ZM3.1758,0.4883c1.4866,0 2.6855,1.2009 2.6855,2.6875 0,1.4866 -1.1989,2.6855 -2.6855,2.6855 -1.4866,0 -2.6875,-1.1989 -2.6875,-2.6855 0,-1.4866 1.2009,-2.6875 2.6875,-2.6875z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeLineCap="square"/> + <path + android:pathData="M3.1755,0.4883 L1.4855,3.4146H2.5829V5.9667H3.1755,3.7671V3.4146h1.0974z" + android:strokeWidth="0" + android:fillColor="#1a1a1a" + android:strokeColor="#1a1a1a" + android:strokeLineCap="square"/> +</vector> diff --git a/androidApp/src/main/res/layout/login.xml b/androidApp/src/main/res/layout/login.xml index 1de3f12..ec3925c 100644 --- a/androidApp/src/main/res/layout/login.xml +++ b/androidApp/src/main/res/layout/login.xml @@ -1,132 +1,151 @@ <?xml version="1.0" encoding="utf-8"?> -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:name="mx.trackermap.TrackerMap.android.session.LoginFragment" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <androidx.constraintlayout.widget.ConstraintLayout +<androidx.constraintlayout.widget.ConstraintLayout + android:name="mx.trackermap.TrackerMap.android.session.LoginFragment" + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> - <androidx.appcompat.widget.AppCompatImageView - android:id="@+id/bannerImage" - android:layout_width="0dp" - android:layout_height="@dimen/about_logo_height" - app:layout_constraintWidth_percent="0.5" - app:layout_constraintDimensionRatio="1" + <include + android:id="@+id/offlineBanner" + layout="@layout/offline_banner" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - android:layout_marginHorizontal="@dimen/card_margin" - app:srcCompat="@drawable/about_logo" /> - - <androidx.cardview.widget.CardView - android:layout_width="0dp" - android:layout_height="wrap_content" - app:cardCornerRadius="@dimen/card_border_radius" - app:cardElevation="@dimen/card_elevation" - app:cardUseCompatPadding="true" - app:layout_constraintTop_toBottomOf="@id/bannerImage" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" - android:layout_marginHorizontal="@dimen/card_margin" - android:layout_marginBottom="16dp"> + android:visibility="gone" + tools:visibility="visible" /> - <androidx.constraintlayout.widget.ConstraintLayout - android:layout_width="match_parent" + <FrameLayout + android:layout_width="match_parent" + android:layout_height="0dp" + app:layout_constraintTop_toBottomOf="@id/offlineBanner" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <androidx.appcompat.widget.AppCompatImageView + android:id="@+id/bannerImage" + android:layout_width="0dp" + android:layout_height="@dimen/about_logo_height" + app:layout_constraintWidth_percent="0.5" + app:layout_constraintDimensionRatio="1" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + android:layout_marginHorizontal="@dimen/card_margin" + app:srcCompat="@drawable/about_logo" /> + + <androidx.cardview.widget.CardView + android:layout_width="0dp" android:layout_height="wrap_content" - android:paddingVertical="@dimen/card_large_padding" - android:paddingHorizontal="@dimen/card_padding"> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/usernameInputLayout" - style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" + app:cardCornerRadius="@dimen/card_border_radius" + app:cardElevation="@dimen/card_elevation" + app:cardUseCompatPadding="true" + app:layout_constraintTop_toBottomOf="@id/bannerImage" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + android:layout_marginHorizontal="@dimen/card_margin" + android:layout_marginBottom="16dp"> + + <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/login_username" - app:layout_constraintBottom_toTopOf="@+id/passwordInputLayout" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.5" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent"> - - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/usernameEditText" + android:paddingVertical="@dimen/card_large_padding" + android:paddingHorizontal="@dimen/card_padding"> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/usernameInputLayout" + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" android:layout_width="match_parent" android:layout_height="wrap_content" - android:inputType="textEmailAddress"/> - - </com.google.android.material.textfield.TextInputLayout> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/passwordInputLayout" - style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="@string/login_password" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.5" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/usernameInputLayout" - app:layout_constraintBottom_toTopOf="@id/urlInputLayout" - android:layout_marginTop="@dimen/fields_spacing"> - - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/passwordEditText" + android:hint="@string/login_username" + app:layout_constraintBottom_toTopOf="@+id/passwordInputLayout" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/usernameEditText" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textEmailAddress"/> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/passwordInputLayout" + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" android:layout_width="match_parent" android:layout_height="wrap_content" - android:inputType="textPassword"/> - - </com.google.android.material.textfield.TextInputLayout> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/urlInputLayout" - style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="@string/login_url" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.5" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/passwordInputLayout" - app:layout_constraintBottom_toTopOf="@id/signinButton" - android:layout_marginTop="@dimen/fields_spacing" - android:visibility="gone"> - - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/urlEditText" + android:hint="@string/login_password" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/usernameInputLayout" + app:layout_constraintBottom_toTopOf="@id/urlInputLayout" + android:layout_marginTop="@dimen/fields_spacing"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/passwordEditText" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textPassword"/> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/urlInputLayout" + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" android:layout_width="match_parent" android:layout_height="wrap_content" - android:inputType="textUri"/> - - </com.google.android.material.textfield.TextInputLayout> - - <com.google.android.material.button.MaterialButton - android:id="@+id/signinButton" - android:layout_width="0dp" - android:layout_height="wrap_content" - app:layout_constraintWidth_percent="0.5" - app:layout_constraintTop_toBottomOf="@id/urlInputLayout" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintHorizontal_bias="0.5" - android:layout_marginTop="@dimen/fields_large_spacing" - android:text="@string/login_login"/> - - </androidx.constraintlayout.widget.ConstraintLayout> - - </androidx.cardview.widget.CardView> - - </androidx.constraintlayout.widget.ConstraintLayout> - - <include - android:id="@+id/infoLoading" - android:layout_width="match_parent" - android:layout_height="match_parent" - layout="@layout/loading_indicator" - android:visibility="gone"/> - -</FrameLayout>
\ No newline at end of file + android:hint="@string/login_url" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/passwordInputLayout" + app:layout_constraintBottom_toTopOf="@id/signinButton" + android:layout_marginTop="@dimen/fields_spacing" + android:visibility="gone"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/urlEditText" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textUri"/> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.button.MaterialButton + android:id="@+id/signinButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintWidth_percent="0.5" + app:layout_constraintTop_toBottomOf="@id/urlInputLayout" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="0.5" + android:layout_marginTop="@dimen/fields_large_spacing" + android:text="@string/login_login"/> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </androidx.cardview.widget.CardView> + + </androidx.constraintlayout.widget.ConstraintLayout> + + <include + android:id="@+id/infoLoading" + android:layout_width="match_parent" + android:layout_height="match_parent" + layout="@layout/loading_indicator" + android:visibility="gone"/> + + </FrameLayout> +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/androidApp/src/main/res/layout/offline_banner.xml b/androidApp/src/main/res/layout/offline_banner.xml new file mode 100644 index 0000000..6710d08 --- /dev/null +++ b/androidApp/src/main/res/layout/offline_banner.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="10dp" + android:background="@color/colorAccent"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAlignment="center" + android:text="@string/login_no_internet" + android:textColor="@android:color/white" /> + +</FrameLayout>
\ No newline at end of file diff --git a/androidApp/src/main/res/layout/units_activity.xml b/androidApp/src/main/res/layout/units_activity.xml index 60a77db..8f46516 100644 --- a/androidApp/src/main/res/layout/units_activity.xml +++ b/androidApp/src/main/res/layout/units_activity.xml @@ -15,7 +15,16 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent"/> + app:layout_constraintTop_toTopOf="parent" /> + + <include + android:id="@+id/offlineBanner" + layout="@layout/offline_banner" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + android:visibility="gone" + tools:visibility="visible" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/displayModeToggle" @@ -28,7 +37,7 @@ app:elevation="@dimen/fab_elevation" app:fabSize="mini" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" + app:layout_constraintTop_toBottomOf="@id/offlineBanner" tools:ignore="ContentDescription" /> <com.google.android.material.card.MaterialCardView @@ -83,7 +92,7 @@ app:elevation="@dimen/fab_elevation" app:fabSize="mini" app:layout_constraintEnd_toStartOf="@id/userButton" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toBottomOf="@id/offlineBanner" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/userButton" @@ -98,6 +107,6 @@ app:elevation="@dimen/fab_elevation" app:fabSize="mini" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent"/> + app:layout_constraintTop_toBottomOf="@id/offlineBanner" /> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/androidApp/src/main/res/values-es-rMX/strings.xml b/androidApp/src/main/res/values-es-rMX/strings.xml index 53e74a2..2b9994a 100644 --- a/androidApp/src/main/res/values-es-rMX/strings.xml +++ b/androidApp/src/main/res/values-es-rMX/strings.xml @@ -19,6 +19,7 @@ <string name="login_password_missing">Falta la contraseña</string> <string name="login_url_missing">Falta la URL del servidor</string> <string name="login_login_failed">Falló el inicio de sesión</string> + <string name="login_no_internet">No hay conexión a internet</string> <!-- UnitsActivity --> <string name="toggle_list">Cambiar a lista de dispositivos</string> diff --git a/androidApp/src/main/res/values/dimen.xml b/androidApp/src/main/res/values/dimen.xml index 8c8c5f5..4afaf23 100644 --- a/androidApp/src/main/res/values/dimen.xml +++ b/androidApp/src/main/res/values/dimen.xml @@ -29,10 +29,12 @@ <dimen name="nav_height">64dp</dimen> <!-- Map --> + <dimen name="marker_size">55dp</dimen> + <dimen name="vertex_size">24dp</dimen> <dimen name="marker_label_text_size">11sp</dimen> <dimen name="geofence_label_text_size">11sp</dimen> <dimen name="geofence_label_width">4dp</dimen> - <dimen name="report_label_width">10dp</dimen> + <dimen name="report_label_width">12dp</dimen> <dimen name="attribution_text_size">11sp</dimen> <!-- Reports bottom sheet --> diff --git a/androidApp/src/main/res/values/strings.xml b/androidApp/src/main/res/values/strings.xml index 3177ad6..3dacd7a 100644 --- a/androidApp/src/main/res/values/strings.xml +++ b/androidApp/src/main/res/values/strings.xml @@ -33,6 +33,7 @@ <string name="login_password_missing">Password is missing</string> <string name="login_url_missing">Server URL is missing</string> <string name="login_login_failed">Login failed</string> + <string name="login_no_internet">No internet connection</string> <!-- UnitsActivity --> <string name="toggle_list">Switch to device list</string> diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 031c33e..471e3b0 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -30,6 +30,7 @@ E36A5A8627B4BFC40070DED5 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = E36A5A8527B4BFC40070DED5 /* FirebaseMessaging */; }; E36DF77B27AB740C003C561C /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E36DF77927AB740C003C561C /* MapViewController.swift */; }; E36DF77C27AB740C003C561C /* MapViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E36DF77A27AB740C003C561C /* MapViewController.xib */; }; + E37F5CA92ABD26DE00EA3234 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = E37F5CA82ABD26DE00EA3234 /* Constants.swift */; }; E38F241527A242870069FC45 /* Inject.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38F241427A242870069FC45 /* Inject.swift */; }; E38F241727A242C70069FC45 /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38F241627A242C70069FC45 /* Resolver.swift */; }; E38F241C27A26DD70069FC45 /* RootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38F241B27A26DD70069FC45 /* RootViewModel.swift */; }; @@ -51,6 +52,7 @@ E3C651E727CB5426002F6F4C /* Tabler in Frameworks */ = {isa = PBXBuildFile; productRef = E3C651E627CB5426002F6F4C /* Tabler */; }; E3C651EA27CB61EE002F6F4C /* GEOSwift in Frameworks */ = {isa = PBXBuildFile; productRef = E3C651E927CB61EE002F6F4C /* GEOSwift */; }; E3E77EE6279E6CE400150070 /* FlowCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3E77EE5279E6CE400150070 /* FlowCollector.swift */; }; + E3F5F2792AB8080E008A47A7 /* OfflineBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3F5F2782AB8080E008A47A7 /* OfflineBanner.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -65,14 +67,14 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; - E3B5741D27F6A3C70018AFCF /* Embed App Extensions */ = { + E3B5741D27F6A3C70018AFCF /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( ); - name = "Embed App Extensions"; + name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ @@ -103,6 +105,7 @@ E36A5A8927B4C8BB0070DED5 /* iosApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iosApp.entitlements; sourceTree = "<group>"; }; E36DF77927AB740C003C561C /* MapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewController.swift; sourceTree = "<group>"; }; E36DF77A27AB740C003C561C /* MapViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MapViewController.xib; sourceTree = "<group>"; }; + E37F5CA82ABD26DE00EA3234 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; }; E38F241427A242870069FC45 /* Inject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inject.swift; sourceTree = "<group>"; }; E38F241627A242C70069FC45 /* Resolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = "<group>"; }; E38F241B27A26DD70069FC45 /* RootViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModel.swift; sourceTree = "<group>"; }; @@ -121,6 +124,7 @@ E3B5740727F68F5F0018AFCF /* XlsxFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XlsxFile.swift; sourceTree = "<group>"; }; E3B5740927F69F750018AFCF /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; }; E3E77EE5279E6CE400150070 /* FlowCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowCollector.swift; sourceTree = "<group>"; }; + E3F5F2782AB8080E008A47A7 /* OfflineBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineBanner.swift; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -287,6 +291,8 @@ E31D721427CF159900CDA320 /* SidewaysScroller.swift */, E3B5740727F68F5F0018AFCF /* XlsxFile.swift */, E3B5740927F69F750018AFCF /* ShareViewController.swift */, + E3F5F2782AB8080E008A47A7 /* OfflineBanner.swift */, + E37F5CA82ABD26DE00EA3234 /* Constants.swift */, ); path = Shared; sourceTree = "<group>"; @@ -303,7 +309,7 @@ 7555FF78242A565900829871 /* Frameworks */, 7555FF79242A565900829871 /* Resources */, E33A236F27A7545500DD647F /* Embed Frameworks */, - E3B5741D27F6A3C70018AFCF /* Embed App Extensions */, + E3B5741D27F6A3C70018AFCF /* Embed Foundation Extensions */, ); buildRules = ( ); @@ -326,8 +332,9 @@ 7555FF73242A565900829871 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1320; - LastUpgradeCheck = 1130; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = orgName; TargetAttributes = { 7555FF7A242A565900829871 = { @@ -420,11 +427,13 @@ E3385A4F27B0D8A10025311C /* UnitCommandsViewModel.swift in Sources */, E392BE1727B77B7E002698F3 /* AppDelegate.swift in Sources */, E36DF77B27AB740C003C561C /* MapViewController.swift in Sources */, + E3F5F2792AB8080E008A47A7 /* OfflineBanner.swift in Sources */, E33A237327A7581A00DD647F /* Utils.swift in Sources */, E3E77EE6279E6CE400150070 /* FlowCollector.swift in Sources */, E33A236527A530F300DD647F /* SmallLabelStyle.swift in Sources */, E39ABC4327A4E88C00965D05 /* UnitsViewModel.swift in Sources */, E360251B27BCA8A600958B21 /* AccountViewModel.swift in Sources */, + E37F5CA92ABD26DE00EA3234 /* Constants.swift in Sources */, E34A2F4827A7878200AD8AEB /* HyperlinkText.swift in Sources */, E3B5740827F68F5F0018AFCF /* XlsxFile.swift in Sources */, 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, @@ -456,6 +465,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -517,6 +527,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -577,7 +588,7 @@ CODE_SIGN_ENTITLEMENTS = iosApp/iosApp.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; DEVELOPMENT_TEAM = 358YRZ9P3L; ENABLE_PREVIEWS = YES; @@ -593,7 +604,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.4; + MARKETING_VERSION = 1.5; OTHER_LDFLAGS = ( "$(inherited)", "-framework", @@ -618,7 +629,7 @@ CODE_SIGN_ENTITLEMENTS = iosApp/iosApp.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; DEVELOPMENT_TEAM = 358YRZ9P3L; ENABLE_PREVIEWS = YES; @@ -634,7 +645,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.4; + MARKETING_VERSION = 1.5; OTHER_LDFLAGS = ( "$(inherited)", "-framework", diff --git a/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme b/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme index 5b29c0a..9ece2b5 100644 --- a/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme +++ b/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <Scheme - LastUpgradeVersion = "1320" + LastUpgradeVersion = "1430" version = "1.3"> <BuildAction parallelizeBuildables = "YES" diff --git a/iosApp/iosApp/Assets.xcassets/Angle0.imageset/0 2.svg b/iosApp/iosApp/Assets.xcassets/Angle0.imageset/0 2.svg new file mode 100644 index 0000000..efe814f --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle0.imageset/0 2.svg @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="24" + height="24" + viewBox="0 0 6.3499999 6.35" + version="1.1" + id="svg1" + sodipodi:docname="0.svg" + inkscape:version="1.3 (0e150ed6c4, 2023-07-21)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:document-units="px" + inkscape:zoom="15.12" + inkscape:cx="13.85582" + inkscape:cy="17.195767" + inkscape:window-width="1920" + inkscape:window-height="1008" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" /> + <defs + id="defs1" /> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="path1" + style="stroke-width:1.00012;stroke-dasharray:none" + transform="rotate(90,3.1749477,3.17515)"> + <path + style="color:#000000;fill:#008000;stroke-width:1.00012;stroke-linecap:square;stroke-dasharray:none" + d="M 6.1057692,3.175 A 2.9307692,2.9307692 0 0 1 3.175,6.1057692 2.9307692,2.9307692 0 0 1 0.24423075,3.175 2.9307692,2.9307692 0 0 1 3.175,0.24423075 2.9307692,2.9307692 0 0 1 6.1057692,3.175 Z" + id="path6" /> + <path + style="color:#000000;fill:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="M 3.1757813,0 C 1.4251716,0 0,1.4251716 0,3.1757813 c 0,1.7506096 1.4251716,3.1738281 3.1757813,3.1738281 1.7506096,0 3.1738281,-1.4232185 3.1738281,-3.1738281 C 6.3496094,1.4251716 4.9263909,0 3.1757813,0 Z m 0,0.48828125 c 1.4866254,0 2.6855468,1.20087455 2.6855468,2.68750005 0,1.4866254 -1.1989214,2.6855468 -2.6855468,2.6855468 -1.4866255,0 -2.68750005,-1.1989214 -2.68750005,-2.6855468 0,-1.4866255 1.20087455,-2.68750005 2.68750005,-2.68750005 z" + id="path7" /> + </g> + <path + id="path5" + style="fill:#1a1a1a;fill-opacity:1;stroke:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="M 5.8618164,3.1756595 2.9355056,1.4857149 V 2.5830814 L 0.24432832,2.5826243 c 0,0 -2e-8,0.395052 -2e-8,0.592578 0,0.1972212 0,0.5916636 0,0.5916636 l 2.6911773,4.572e-4 v 1.0973665 z" + sodipodi:nodetypes="ccccscccc" /> + </g> +</svg> diff --git a/iosApp/iosApp/Assets.xcassets/Angle0.imageset/Contents.json b/iosApp/iosApp/Assets.xcassets/Angle0.imageset/Contents.json new file mode 100644 index 0000000..be69468 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle0.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "0 2.svg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/Angle135.imageset/135.svg b/iosApp/iosApp/Assets.xcassets/Angle135.imageset/135.svg new file mode 100644 index 0000000..a7c625b --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle135.imageset/135.svg @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="24" + height="24" + viewBox="0 0 6.3499999 6.35" + version="1.1" + id="svg1" + sodipodi:docname="135.svg" + inkscape:version="1.3 (0e150ed6c4, 2023-07-21)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:document-units="px" + inkscape:zoom="21.382909" + inkscape:cx="8.6283863" + inkscape:cy="10.896553" + inkscape:window-width="1920" + inkscape:window-height="1008" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" /> + <defs + id="defs1" /> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="path1" + style="stroke-width:1.00012;stroke-dasharray:none" + transform="rotate(-45,3.175293,3.1743164)"> + <path + style="color:#000000;fill:#008000;stroke-width:1.00012;stroke-linecap:square;stroke-dasharray:none" + d="M 6.1057692,3.175 A 2.9307692,2.9307692 0 0 1 3.175,6.1057692 2.9307692,2.9307692 0 0 1 0.24423075,3.175 2.9307692,2.9307692 0 0 1 3.175,0.24423075 2.9307692,2.9307692 0 0 1 6.1057692,3.175 Z" + id="path6" /> + <path + style="color:#000000;fill:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="M 3.1757813,0 C 1.4251716,0 0,1.4251716 0,3.1757813 c 0,1.7506096 1.4251716,3.1738281 3.1757813,3.1738281 1.7506096,0 3.1738281,-1.4232185 3.1738281,-3.1738281 C 6.3496094,1.4251716 4.9263909,0 3.1757813,0 Z m 0,0.48828125 c 1.4866254,0 2.6855468,1.20087455 2.6855468,2.68750005 0,1.4866254 -1.1989214,2.6855468 -2.6855468,2.6855468 -1.4866255,0 -2.68750005,-1.1989214 -2.68750005,-2.6855468 0,-1.4866255 1.20087455,-2.68750005 2.68750005,-2.68750005 z" + id="path7" /> + </g> + <path + id="path5" + style="fill:#1a1a1a;fill-opacity:1;stroke:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="M 1.2760955,1.2748867 2.1503383,4.5390721 2.9262936,3.7631168 4.82892,5.6663897 c 0,0 0.279344,-0.279344 0.419016,-0.4190159 C 5.3873924,5.1079173 5.6663053,4.8290044 5.6663053,4.8290044 L 3.763679,2.9257315 4.5396343,2.1497762 Z" + sodipodi:nodetypes="ccccscccc" /> + </g> +</svg> diff --git a/iosApp/iosApp/Assets.xcassets/Angle135.imageset/Contents.json b/iosApp/iosApp/Assets.xcassets/Angle135.imageset/Contents.json new file mode 100644 index 0000000..1e01d4c --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle135.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "135.svg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/Angle180.imageset/180.svg b/iosApp/iosApp/Assets.xcassets/Angle180.imageset/180.svg new file mode 100644 index 0000000..4df8b75 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle180.imageset/180.svg @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="24" + height="24" + viewBox="0 0 6.3499999 6.35" + version="1.1" + id="svg1" + sodipodi:docname="180.svg" + inkscape:version="1.3 (0e150ed6c4, 2023-07-21)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:document-units="px" + inkscape:zoom="21.382909" + inkscape:cx="8.6283863" + inkscape:cy="10.896553" + inkscape:window-width="1920" + inkscape:window-height="1008" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" /> + <defs + id="defs1" /> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="path1" + style="stroke-width:1.00012;stroke-dasharray:none" + transform="rotate(-90,3.17515,3.1746617)"> + <path + style="color:#000000;fill:#008000;stroke-width:1.00012;stroke-linecap:square;stroke-dasharray:none" + d="M 6.1057692,3.175 A 2.9307692,2.9307692 0 0 1 3.175,6.1057692 2.9307692,2.9307692 0 0 1 0.24423075,3.175 2.9307692,2.9307692 0 0 1 3.175,0.24423075 2.9307692,2.9307692 0 0 1 6.1057692,3.175 Z" + id="path6" /> + <path + style="color:#000000;fill:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="M 3.1757813,0 C 1.4251716,0 0,1.4251716 0,3.1757813 c 0,1.7506096 1.4251716,3.1738281 3.1757813,3.1738281 1.7506096,0 3.1738281,-1.4232185 3.1738281,-3.1738281 C 6.3496094,1.4251716 4.9263909,0 3.1757813,0 Z m 0,0.48828125 c 1.4866254,0 2.6855468,1.20087455 2.6855468,2.68750005 0,1.4866254 -1.1989214,2.6855468 -2.6855468,2.6855468 -1.4866255,0 -2.68750005,-1.1989214 -2.68750005,-2.6855468 0,-1.4866255 1.20087455,-2.68750005 2.68750005,-2.68750005 z" + id="path7" /> + </g> + <path + id="path5" + style="fill:#1a1a1a;fill-opacity:1;stroke:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="M 0.4887696,3.1743545 3.4150803,4.8642991 V 3.7669326 l 2.6911772,4.571e-4 c 0,0 0,-0.395052 0,-0.592578 0,-0.1972212 0,-0.5916636 0,-0.5916636 L 3.4150803,2.5826909 V 1.4853244 Z" + sodipodi:nodetypes="ccccscccc" /> + </g> +</svg> diff --git a/iosApp/iosApp/Assets.xcassets/Angle180.imageset/Contents.json b/iosApp/iosApp/Assets.xcassets/Angle180.imageset/Contents.json new file mode 100644 index 0000000..cca2175 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle180.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "180.svg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/Angle225.imageset/225.svg b/iosApp/iosApp/Assets.xcassets/Angle225.imageset/225.svg new file mode 100644 index 0000000..35d4080 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle225.imageset/225.svg @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="24" + height="24" + viewBox="0 0 6.3499999 6.35" + version="1.1" + id="svg1" + sodipodi:docname="225.svg" + inkscape:version="1.3 (0e150ed6c4, 2023-07-21)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:document-units="px" + inkscape:zoom="21.382909" + inkscape:cx="8.6283863" + inkscape:cy="10.896553" + inkscape:window-width="1920" + inkscape:window-height="1008" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" /> + <defs + id="defs1" /> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="path1" + style="stroke-width:1.00012;stroke-dasharray:none" + transform="rotate(-135,3.175579,3.175007)"> + <path + style="color:#000000;fill:#008000;stroke-width:1.00012;stroke-linecap:square;stroke-dasharray:none" + d="M 6.1057692,3.175 A 2.9307692,2.9307692 0 0 1 3.175,6.1057692 2.9307692,2.9307692 0 0 1 0.24423075,3.175 2.9307692,2.9307692 0 0 1 3.175,0.24423075 2.9307692,2.9307692 0 0 1 6.1057692,3.175 Z" + id="path6" /> + <path + style="color:#000000;fill:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="M 3.1757813,0 C 1.4251716,0 0,1.4251716 0,3.1757813 c 0,1.7506096 1.4251716,3.1738281 3.1757813,3.1738281 1.7506096,0 3.1738281,-1.4232185 3.1738281,-3.1738281 C 6.3496094,1.4251716 4.9263909,0 3.1757813,0 Z m 0,0.48828125 c 1.4866254,0 2.6855468,1.20087455 2.6855468,2.68750005 0,1.4866254 -1.1989214,2.6855468 -2.6855468,2.6855468 -1.4866255,0 -2.68750005,-1.1989214 -2.68750005,-2.6855468 0,-1.4866255 1.20087455,-2.68750005 2.68750005,-2.68750005 z" + id="path7" /> + </g> + <path + id="path5" + style="fill:#1a1a1a;fill-opacity:1;stroke:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="M 1.2758633,5.074895 4.5400487,4.2006522 3.7640934,3.4246969 5.6673663,1.5220705 c 0,0 -0.279344,-0.279344 -0.4190159,-0.419016 C 5.1088939,0.96359809 4.829981,0.68468519 4.829981,0.68468519 L 2.9267081,2.5873115 2.1507528,1.8113562 Z" + sodipodi:nodetypes="ccccscccc" /> + </g> +</svg> diff --git a/iosApp/iosApp/Assets.xcassets/Angle225.imageset/Contents.json b/iosApp/iosApp/Assets.xcassets/Angle225.imageset/Contents.json new file mode 100644 index 0000000..1b49b8a --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle225.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "225.svg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/Angle270.imageset/270.svg b/iosApp/iosApp/Assets.xcassets/Angle270.imageset/270.svg new file mode 100644 index 0000000..23562a0 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle270.imageset/270.svg @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="24" + height="24" + viewBox="0 0 6.3499999 6.35" + version="1.1" + id="svg1" + sodipodi:docname="270.svg" + inkscape:version="1.3 (0e150ed6c4, 2023-07-21)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:document-units="px" + inkscape:zoom="21.382909" + inkscape:cx="8.6283863" + inkscape:cy="10.896553" + inkscape:window-width="1920" + inkscape:window-height="1008" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" /> + <defs + id="defs1" /> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="path1" + style="stroke-width:1.00012;stroke-dasharray:none" + transform="rotate(-180,3.1756382,3.17515)"> + <path + style="color:#000000;fill:#008000;stroke-width:1.00012;stroke-linecap:square;stroke-dasharray:none" + d="M 6.1057692,3.175 A 2.9307692,2.9307692 0 0 1 3.175,6.1057692 2.9307692,2.9307692 0 0 1 0.24423075,3.175 2.9307692,2.9307692 0 0 1 3.175,0.24423075 2.9307692,2.9307692 0 0 1 6.1057692,3.175 Z" + id="path6" /> + <path + style="color:#000000;fill:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="M 3.1757813,0 C 1.4251716,0 0,1.4251716 0,3.1757813 c 0,1.7506096 1.4251716,3.1738281 3.1757813,3.1738281 1.7506096,0 3.1738281,-1.4232185 3.1738281,-3.1738281 C 6.3496094,1.4251716 4.9263909,0 3.1757813,0 Z m 0,0.48828125 c 1.4866254,0 2.6855468,1.20087455 2.6855468,2.68750005 0,1.4866254 -1.1989214,2.6855468 -2.6855468,2.6855468 -1.4866255,0 -2.68750005,-1.1989214 -2.68750005,-2.6855468 0,-1.4866255 1.20087455,-2.68750005 2.68750005,-2.68750005 z" + id="path7" /> + </g> + <path + id="path5" + style="fill:#1a1a1a;fill-opacity:1;stroke:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="m 3.1758193,5.8620185 1.6899446,-2.9263106 -1.0973665,0 4.571e-4,-2.6911772 c 0,0 -0.395052,0 -0.592578,-7e-8 -0.1972212,7e-8 -0.5916636,7e-8 -0.5916636,7e-8 l -4.572e-4,2.6911771 -1.0973665,0 z" + sodipodi:nodetypes="ccccscccc" /> + </g> +</svg> diff --git a/iosApp/iosApp/Assets.xcassets/Angle270.imageset/Contents.json b/iosApp/iosApp/Assets.xcassets/Angle270.imageset/Contents.json new file mode 100644 index 0000000..9eab5a3 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle270.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "270.svg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/Angle315.imageset/315.svg b/iosApp/iosApp/Assets.xcassets/Angle315.imageset/315.svg new file mode 100644 index 0000000..a501605 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle315.imageset/315.svg @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="24" + height="24" + viewBox="0 0 6.3499999 6.35" + version="1.1" + id="svg1" + sodipodi:docname="315.svg" + inkscape:version="1.3 (0e150ed6c4, 2023-07-21)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:document-units="px" + inkscape:zoom="21.382909" + inkscape:cx="8.6283863" + inkscape:cy="11.972178" + inkscape:window-width="1920" + inkscape:window-height="1008" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" /> + <defs + id="defs1" /> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="path1" + style="stroke-width:1.00012;stroke-dasharray:none" + transform="rotate(135,3.1754952,3.1754952)"> + <path + style="color:#000000;fill:#008000;stroke-width:1.00012;stroke-linecap:square;stroke-dasharray:none" + d="M 6.1057692,3.175 A 2.9307692,2.9307692 0 0 1 3.175,6.1057692 2.9307692,2.9307692 0 0 1 0.24423075,3.175 2.9307692,2.9307692 0 0 1 3.175,0.24423075 2.9307692,2.9307692 0 0 1 6.1057692,3.175 Z" + id="path6" /> + <path + style="color:#000000;fill:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="M 3.1757813,0 C 1.4251716,0 0,1.4251716 0,3.1757813 c 0,1.7506096 1.4251716,3.1738281 3.1757813,3.1738281 1.7506096,0 3.1738281,-1.4232185 3.1738281,-3.1738281 C 6.3496094,1.4251716 4.9263909,0 3.1757813,0 Z m 0,0.48828125 c 1.4866254,0 2.6855468,1.20087455 2.6855468,2.68750005 0,1.4866254 -1.1989214,2.6855468 -2.6855468,2.6855468 -1.4866255,0 -2.68750005,-1.1989214 -2.68750005,-2.6855468 0,-1.4866255 1.20087455,-2.68750005 2.68750005,-2.68750005 z" + id="path7" /> + </g> + <path + id="path5" + style="fill:#1a1a1a;fill-opacity:1;stroke:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="M 5.0756692,5.0756154 4.2014264,1.81143 3.4254711,2.5873853 1.5228447,0.68411246 c 0,0 -0.279344,0.27934395 -0.419016,0.41901584 C 0.96437228,1.2425848 0.68545938,1.5214977 0.68545938,1.5214977 L 2.5880857,3.4247706 1.8121304,4.2007259 Z" + sodipodi:nodetypes="ccccscccc" /> + </g> +</svg> diff --git a/iosApp/iosApp/Assets.xcassets/Angle315.imageset/Contents.json b/iosApp/iosApp/Assets.xcassets/Angle315.imageset/Contents.json new file mode 100644 index 0000000..fed8257 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle315.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "315.svg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/Angle45.imageset/45.svg b/iosApp/iosApp/Assets.xcassets/Angle45.imageset/45.svg new file mode 100644 index 0000000..1ce060e --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle45.imageset/45.svg @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="24" + height="24" + viewBox="0 0 6.3499999 6.35" + version="1.1" + id="svg1" + sodipodi:docname="45.svg" + inkscape:version="1.3 (0e150ed6c4, 2023-07-21)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:document-units="px" + inkscape:zoom="15.12" + inkscape:cx="13.062169" + inkscape:cy="10.780423" + inkscape:window-width="1920" + inkscape:window-height="1008" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" /> + <defs + id="defs1" /> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="path1" + style="stroke-width:1.00012;stroke-dasharray:none" + transform="rotate(45,3.1743164,3.1743164)"> + <path + style="color:#000000;fill:#008000;stroke-width:1.00012;stroke-linecap:square;stroke-dasharray:none" + d="M 6.1057692,3.175 A 2.9307692,2.9307692 0 0 1 3.175,6.1057692 2.9307692,2.9307692 0 0 1 0.24423075,3.175 2.9307692,2.9307692 0 0 1 3.175,0.24423075 2.9307692,2.9307692 0 0 1 6.1057692,3.175 Z" + id="path6" /> + <path + style="color:#000000;fill:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="M 3.1757813,0 C 1.4251716,0 0,1.4251716 0,3.1757813 c 0,1.7506096 1.4251716,3.1738281 3.1757813,3.1738281 1.7506096,0 3.1738281,-1.4232185 3.1738281,-3.1738281 C 6.3496094,1.4251716 4.9263909,0 3.1757813,0 Z m 0,0.48828125 c 1.4866254,0 2.6855468,1.20087455 2.6855468,2.68750005 0,1.4866254 -1.1989214,2.6855468 -2.6855468,2.6855468 -1.4866255,0 -2.68750005,-1.1989214 -2.68750005,-2.6855468 0,-1.4866255 1.20087455,-2.68750005 2.68750005,-2.68750005 z" + id="path7" /> + </g> + <path + id="path5" + style="fill:#1a1a1a;fill-opacity:1;stroke:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="M 5.0744368,1.2758094 1.8102513,2.1500524 2.5862066,2.9260077 0.80607031,4.7066972 1.2250863,5.1257132 1.6434556,5.5440825 3.4235919,3.7633929 4.1995472,4.5393483 Z" + sodipodi:nodetypes="ccccccccc" /> + </g> +</svg> diff --git a/iosApp/iosApp/Assets.xcassets/Angle45.imageset/Contents.json b/iosApp/iosApp/Assets.xcassets/Angle45.imageset/Contents.json new file mode 100644 index 0000000..a785b47 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle45.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "45.svg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/Angle90.imageset/90.svg b/iosApp/iosApp/Assets.xcassets/Angle90.imageset/90.svg new file mode 100644 index 0000000..de994b5 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle90.imageset/90.svg @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="24" + height="24" + viewBox="0 0 6.3499999 6.35" + version="1.1" + id="svg1" + inkscape:version="1.3 (0e150ed6c4, 2023-07-21)" + sodipodi:docname="90.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:document-units="px" + inkscape:zoom="15.12" + inkscape:cx="17.956349" + inkscape:cy="11.177249" + inkscape:window-width="1920" + inkscape:window-height="1008" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" /> + <defs + id="defs1" /> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="path1" + style="stroke-width:1.00012499;stroke-dasharray:none"> + <path + style="color:#000000;fill:#008000;stroke-linecap:square;stroke-width:1.00012499;stroke-dasharray:none" + d="M 6.1057692,3.175 A 2.9307692,2.9307692 0 0 1 3.175,6.1057692 2.9307692,2.9307692 0 0 1 0.24423075,3.175 2.9307692,2.9307692 0 0 1 3.175,0.24423075 2.9307692,2.9307692 0 0 1 6.1057692,3.175 Z" + id="path6" /> + <path + style="color:#000000;fill:#1a1a1a;stroke-linecap:square;stroke-width:0;stroke-dasharray:none" + d="M 3.1757813,0 C 1.4251716,0 0,1.4251716 0,3.1757813 c 0,1.7506096 1.4251716,3.1738281 3.1757813,3.1738281 1.7506096,0 3.1738281,-1.4232185 3.1738281,-3.1738281 C 6.3496094,1.4251716 4.9263909,0 3.1757813,0 Z m 0,0.48828125 c 1.4866254,0 2.6855468,1.20087455 2.6855468,2.68750005 0,1.4866254 -1.1989214,2.6855468 -2.6855468,2.6855468 -1.4866255,0 -2.68750005,-1.1989214 -2.68750005,-2.6855468 0,-1.4866255 1.20087455,-2.68750005 2.68750005,-2.68750005 z" + id="path7" /> + </g> + <path + id="path5" + style="fill:#1a1a1a;fill-opacity:1;stroke:#1a1a1a;stroke-width:0;stroke-linecap:square;stroke-dasharray:none" + d="M 3.1754573,0.48828127 1.4855127,3.414592 H 2.5828792 V 5.9667127 H 3.1754573 3.7671208 V 3.414592 h 1.0973665 z" + sodipodi:nodetypes="ccccscccc" /> + </g> +</svg> diff --git a/iosApp/iosApp/Assets.xcassets/Angle90.imageset/Contents.json b/iosApp/iosApp/Assets.xcassets/Angle90.imageset/Contents.json new file mode 100644 index 0000000..c84e36d --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Angle90.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "90.svg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Details/Reports/UnitReportsViewModel.swift b/iosApp/iosApp/Details/Reports/UnitReportsViewModel.swift index 7e2ba81..4e0d39e 100644 --- a/iosApp/iosApp/Details/Reports/UnitReportsViewModel.swift +++ b/iosApp/iosApp/Details/Reports/UnitReportsViewModel.swift @@ -19,6 +19,7 @@ import Foundation import Combine import shared +@MainActor class UnitReportsViewModel: ObservableObject { @Inject var reportController: ReportController @Inject var geofencesController: GeofencesController @@ -38,25 +39,27 @@ class UnitReportsViewModel: ObservableObject { } @Published var periodType: ReportDates.PeriodTypes = .today { didSet { - switch periodType { - case .today: - reportPeriod = ReportDates.ReportPeriodToday() - case .last24: - reportPeriod = ReportDates.ReportPeriodLast24() - case .yesterday: - reportPeriod = ReportDates.ReportPeriodYesterday() - case .thisWeek: - reportPeriod = ReportDates.ReportPeriodThisWeek() - case .last7: - reportPeriod = ReportDates.ReportPeriodLast7() - case .thisMonth: - reportPeriod = ReportDates.ReportPeriodThisMonth() - case .last30: - reportPeriod = ReportDates.ReportPeriodLast30() - case .custom: - reportPeriod = ReportDates.ReportPeriodCustom(from: nil, to: nil) - default: - reportPeriod = ReportDates.ReportPeriodToday() + Task { @MainActor in + switch periodType { + case .today: + reportPeriod = ReportDates.ReportPeriodToday() + case .last24: + reportPeriod = ReportDates.ReportPeriodLast24() + case .yesterday: + reportPeriod = ReportDates.ReportPeriodYesterday() + case .thisWeek: + reportPeriod = ReportDates.ReportPeriodThisWeek() + case .last7: + reportPeriod = ReportDates.ReportPeriodLast7() + case .thisMonth: + reportPeriod = ReportDates.ReportPeriodThisMonth() + case .last30: + reportPeriod = ReportDates.ReportPeriodLast30() + case .custom: + reportPeriod = ReportDates.ReportPeriodCustom(from: nil, to: nil) + default: + reportPeriod = ReportDates.ReportPeriodToday() + } } } } @@ -136,11 +139,15 @@ class UnitReportsViewModel: ObservableObject { } func setReport (report: ReportController.Report) { - self.report = report + Task { @MainActor in + self.report = report + } } private func setGeofences(geofences: [Int: Geofence]) { - self.geofences = geofences + Task { @MainActor in + self.geofences = geofences + } } func fetchReport(xlsx: Bool = false) { diff --git a/iosApp/iosApp/Map/MapViewController.swift b/iosApp/iosApp/Map/MapViewController.swift index c7bc91d..f424389 100644 --- a/iosApp/iosApp/Map/MapViewController.swift +++ b/iosApp/iosApp/Map/MapViewController.swift @@ -224,14 +224,14 @@ class OurMaplyViewController: MaplyViewController { Float(marker.latitude)) } - let fontSize = 11.0 + let fontSize = Constants.markerLabelTextSize let colorReport = UIColor(red: 0.0, green: 0.5, blue: 0.0, alpha: 1.0) let colorLabel = UIColor.darkGray let colorLabelOutline = UIColor.white let vectorDesc: [AnyHashable : Any] = [ kMaplyColor: colorReport, - kMaplyVecWidth: 12.0, + kMaplyVecWidth: Constants.reportLineWidth, kMaplyWideVecImpl: kMaplyWideVecImplPerf ] @@ -248,26 +248,26 @@ class OurMaplyViewController: MaplyViewController { screenMarker.layoutImportance = .greatestFiniteMagnitude screenMarker.loc = MaplyCoordinateMakeWithDegrees(Float(marker.longitude), Float(marker.latitude)) - var type: Marker.Type_ = .default_ + var image: UIImage if isReport { // For reports, position, start and end icons must be different switch i { - case markers.startIndex: type = .reportStart - case markers.endIndex - 1: type = .reportEnd - default: type = .reportPosition + case markers.startIndex: image = getIcon(markerType: .reportStart) + case markers.endIndex - 1: image = getIcon(markerType: .reportEnd) + default: image = getIcon(forDirection: points[i], to: points[i + 1]) } } else { - type = marker.type + image = getIcon(markerType: marker.type) } - screenMarker.image = getIcon(markerType: type) + screenMarker.image = image - var size = 50.0 + var size = Constants.markerSize if isReport { // For reports, position, start and end sizes must be different switch i { - case markers.startIndex: size = 40.0 - case markers.endIndex - 1: size = 40.0 - default: size = 22.0 + case markers.startIndex: size = Constants.markerSize + case markers.endIndex - 1: size = Constants.markerSize + default: size = Constants.vertexsize } } screenMarker.size = CGSize(width: size, height: size) @@ -311,14 +311,14 @@ class OurMaplyViewController: MaplyViewController { "features": [ [ "type": "Feature", - "properties": [], + "properties": [] as [Any], "geometry": [ "type": "LineString", "coordinates": markers.map({ marker in [marker.longitude, marker.latitude] }) - ] - ] + ] as [String : Any] + ] as [String : Any] ] ] if let vector = MaplyVectorObject(fromGeoJSONDictionary: geoJSON) { @@ -342,14 +342,14 @@ class OurMaplyViewController: MaplyViewController { func display(geofences: [Geofence]) { clear(geofences: true) - let fontSize = 11.0 + let fontSize = Constants.geofenceLabelTextSize let colorFill = UIColor(red: 0.10, green: 0.46, blue: 0.82, alpha: 1.00) let colorLabel = UIColor(red: 0.10, green: 0.46, blue: 0.82, alpha: 1.00) let colorLabelOutline = UIColor.white let vectorDesc: [AnyHashable : Any] = [ kMaplyColor: colorFill, - kMaplyVecWidth: 12.0, + kMaplyVecWidth: Constants.geofenceLineWidth, kMaplyWideVecImpl: kMaplyWideVecImplPerf ] @@ -375,7 +375,7 @@ class OurMaplyViewController: MaplyViewController { "features": [ [ "type": "Feature", - "properties": [], + "properties": [] as [Any], "geometry": [ "type": "Polygon", "coordinates": [ @@ -383,8 +383,8 @@ class OurMaplyViewController: MaplyViewController { [coordinate.y, coordinate.x] } ] - ] - ] + ] as [String : Any] + ] as [String : Any] ] ] if let vector = MaplyVectorObject(fromGeoJSONDictionary: geoJSON) { @@ -439,4 +439,12 @@ class OurMaplyViewController: MaplyViewController { return UIImage(named: MarkerTransformations .markerTypeToImageName(markerType: markerType))! } + + private func getIcon(forDirection a: MaplyCoordinate, to b: MaplyCoordinate) -> UIImage { + let vectorX = b.x - a.x + let vectorY = b.y - a.y + let angleRad = atan2(vectorY, vectorX) + return UIImage(named: MarkerTransformations + .angleToImageName(rad: angleRad))! + } } diff --git a/iosApp/iosApp/Map/UnitMapView.swift b/iosApp/iosApp/Map/UnitMapView.swift index 5a1e2f0..8444e36 100644 --- a/iosApp/iosApp/Map/UnitMapView.swift +++ b/iosApp/iosApp/Map/UnitMapView.swift @@ -27,7 +27,9 @@ struct UnitMapView: View { markers: $unitsViewModel.markers, geofences: $unitsViewModel.flatGeofences, selected: $unitsViewModel.selectedMarker, - markerCallback: unitsViewModel.selectUnitWith) + markerCallback: { p1, p2 in + unitsViewModel.selectUnitWith(position: p1, switchToMap: p2) + }) if let unit = unitsViewModel.selectedUnit { VStack { DeviceRow(unit: unit, callback: { action in diff --git a/iosApp/iosApp/Session/RootView.swift b/iosApp/iosApp/Session/RootView.swift index 960187a..10498b2 100644 --- a/iosApp/iosApp/Session/RootView.swift +++ b/iosApp/iosApp/Session/RootView.swift @@ -35,16 +35,34 @@ struct RootView: View { var body: some View { Group { - switch rootViewModel.loginState { - case is SessionController.LoginStateLoading: - LoadingView() - case is SessionController.LoginStateSuccess: - UnitsView() - default: - LoginContentView(username: $username, - password: $password, - server: $server, - onLogin: rootViewModel.login) + VStack { + if (rootViewModel.networkAvailable != true) { + OfflineBanner() + } + + if rootViewModel.showLoadingView { + LoadingView() + .frame(minHeight: 0, maxHeight: .infinity) + } else { + switch rootViewModel.loginState { + case is SessionController.LoginStateSuccess: + UnitsView() + case is SessionController.LoginStateLoading: + LoadingView() + .frame(minHeight: 0, maxHeight: .infinity) + default: + LoginContentView(username: $username, + password: $password, + server: $server, + onLogin: { username, password, server in + rootViewModel.login( + username: username, + password: password, + url: server + ) + }) + } + } } }.environmentObject(rootViewModel) } diff --git a/iosApp/iosApp/Session/RootViewModel.swift b/iosApp/iosApp/Session/RootViewModel.swift index ec103ba..16f21f3 100644 --- a/iosApp/iosApp/Session/RootViewModel.swift +++ b/iosApp/iosApp/Session/RootViewModel.swift @@ -20,14 +20,42 @@ import shared @MainActor class RootViewModel: ObservableObject { + @Inject private var networkController: NetworkController @Inject private var sessionController: SessionController + @Published var networkAvailable: Bool? = nil @Published var loginState: SessionController.LoginState = SessionController.LoginStateNothing() + @Published var showLoadingView: Bool = false + + var hasSession: Bool{ + get { return sessionController.hasSession } + } init() { - let collector = Collector<SessionController.LoginState?>(callback: setLoginState) - sessionController.loginStateFlow.collect(collector: collector) { _ in } - restoreSession() + let networkCollector = Collector<Bool?>(callback: setNetworkState) + networkController.networkAvailable.collect(collector: networkCollector) { _ in } + let sessionCollector = Collector<SessionController.LoginState?>(callback: setLoginState) + sessionController.loginStateFlow.collect(collector: sessionCollector) { _ in } + } + + func setNetworkState(state: Bool?) { + print("Network state is: \(state?.description ?? "")") + Task { @MainActor in + self.networkAvailable = state + + // Wait for internet to restore session + if (state == true && sessionController.hasSession) { + showLoadingView = false + restoreSession() + return + } + + if (state == nil) { + showLoadingView = true + } else { + showLoadingView = hasSession + } + } } func setLoginState(state: SessionController.LoginState?) { @@ -38,7 +66,7 @@ class RootViewModel: ObservableObject { } func restoreSession() { - sessionController.restoreSession() + sessionController.getSession() } private func getFcmToken() -> String? { diff --git a/iosApp/iosApp/Shared/Constants.swift b/iosApp/iosApp/Shared/Constants.swift new file mode 100644 index 0000000..de82b0b --- /dev/null +++ b/iosApp/iosApp/Shared/Constants.swift @@ -0,0 +1,19 @@ +// +// Constants.swift +// iosApp +// +// Created by Ivan Avalos on 21/09/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import Foundation +import UIKit + +struct Constants { + static var markerSize = 14.0 * UIScreen.main.scale + static var vertexsize = 8.0 * UIScreen.main.scale + static var markerLabelTextSize = UIFont.smallSystemFontSize + static var geofenceLabelTextSize = UIFont.smallSystemFontSize + static var geofenceLineWidth = 4.0 * UIScreen.main.scale + static var reportLineWidth = 4.0 * UIScreen.main.scale +} diff --git a/iosApp/iosApp/Shared/MarkerTransformations.swift b/iosApp/iosApp/Shared/MarkerTransformations.swift index 7291a58..57aa36e 100644 --- a/iosApp/iosApp/Shared/MarkerTransformations.swift +++ b/iosApp/iosApp/Shared/MarkerTransformations.swift @@ -51,4 +51,23 @@ class MarkerTransformations { } return imageName } + + static let STEP = Float.pi / 8 + + static func angleToImageName(rad: Float) -> String { + var imageName: String + switch rad { + case 0.0 ..< STEP: imageName = "Angle0" + case STEP ..< STEP * 3: imageName = "Angle45" + case STEP * 3 ..< STEP * 5: imageName = "Angle90" + case STEP * 5 ..< STEP * 7: imageName = "Angle135" + case STEP * 7 ..< STEP * 9: imageName = "Angle180" + case STEP * 9 ..< STEP * 11: imageName = "Angle225" + case STEP * 11 ..< STEP * 13: imageName = "Angle270" + case STEP * 13 ..< STEP * 15: imageName = "Angle315" + case STEP * 15 ..< STEP * 16: imageName = "Angle0" + default: imageName = angleToImageName(rad: Float.pi * 2 + rad) + } + return imageName + } } diff --git a/iosApp/iosApp/Shared/OfflineBanner.swift b/iosApp/iosApp/Shared/OfflineBanner.swift new file mode 100644 index 0000000..f29ab7d --- /dev/null +++ b/iosApp/iosApp/Shared/OfflineBanner.swift @@ -0,0 +1,27 @@ +// +// OfflineBanner.swift +// iosApp +// +// Created by Ivan Avalos on 17/09/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI + +struct OfflineBanner: View { + var body: some View { + Group { + Text("offline") + .foregroundColor(.white) + .padding(5) + } + .frame(minWidth: 0, maxWidth: .infinity) + .background(Color.red) + } +} + +struct OfflineBanner_Previews: PreviewProvider { + static var previews: some View { + OfflineBanner() + } +} diff --git a/iosApp/iosApp/Units/UnitsViewModel.swift b/iosApp/iosApp/Units/UnitsViewModel.swift index 7fcc80e..52e0c39 100644 --- a/iosApp/iosApp/Units/UnitsViewModel.swift +++ b/iosApp/iosApp/Units/UnitsViewModel.swift @@ -21,6 +21,7 @@ import shared @MainActor class UnitsViewModel: ObservableObject { + @Inject var networkController: NetworkController @Inject var unitsController: UnitsController @Inject var geofenceController: GeofencesController @@ -34,6 +35,7 @@ class UnitsViewModel: ObservableObject { var detailsUnit: UnitInformation? = nil var detailsAction = DeviceRow.Action.details + @Published var networkAvailable: Bool? = nil @Published var searchQuery = "" { didSet { unitsDisplayMode = .list @@ -83,6 +85,9 @@ class UnitsViewModel: ObservableObject { } private func setupObservers() { + let networkCollector = Collector<Bool?>(callback: setNetworkState) + networkController.networkAvailable.collect(collector: networkCollector) { _ in } + let unitsCollector = Collector<[UnitInformation]>(callback: setUnits) unitsController.displayedUnitsFlow.collect(collector: unitsCollector) { _ in } @@ -90,12 +95,19 @@ class UnitsViewModel: ObservableObject { geofenceController.geofencesFlow.collect(collector: geofencesCollector) { _ in } } + private func setNetworkState(state: Bool?) { + print("Network state is: \(state?.description ?? "")") + Task { @MainActor in + self.networkAvailable = state + } + } + private func setUnits(units: [UnitInformation]) { print("Positions") Task { @MainActor in self.units = units + updateSelectedUnit() } - updateSelectedUnit() } private func setGeofences(geofences: [Int: Geofence]) { diff --git a/iosApp/iosApp/en.lproj/Localizable.strings b/iosApp/iosApp/en.lproj/Localizable.strings index 0959a1f..58d1ba5 100644 --- a/iosApp/iosApp/en.lproj/Localizable.strings +++ b/iosApp/iosApp/en.lproj/Localizable.strings @@ -22,6 +22,7 @@ "loading" = "Loading"; "done" = "Done"; +"offline" = "No internet access"; "username" = "Username"; "password" = "Password"; diff --git a/iosApp/iosApp/es-419.lproj/Localizable.strings b/iosApp/iosApp/es-419.lproj/Localizable.strings index 52267cc..8fc5d45 100644 --- a/iosApp/iosApp/es-419.lproj/Localizable.strings +++ b/iosApp/iosApp/es-419.lproj/Localizable.strings @@ -22,6 +22,7 @@ "loading" = "Cargando"; "done" = "Hecho"; +"offline" = "No hay acceso a internet"; "username" = "Usuario"; "password" = "Contraseña"; diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift index 763e0e2..46848ae 100644 --- a/iosApp/iosApp/iOSApp.swift +++ b/iosApp/iosApp/iOSApp.swift @@ -53,8 +53,15 @@ struct iOSApp: App { Resolver.shared.add(CommandsApi.self) { resolver in return CommandsApi(sessionManager: resolver.resolve()) } + Resolver.shared.add(NetworkController.self) { resolver in + return NetworkController() + } Resolver.shared.add(SessionController.self) { resolver in - return SessionController(sessionApi: resolver.resolve(), usersApi: resolver.resolve()) + return SessionController( + sessionManager: resolver.resolve(), + sessionApi: resolver.resolve(), + usersApi: resolver.resolve() + ) } Resolver.shared.add(UnitsController.self) { resolver in return UnitsController(devicesApi: resolver.resolve(), positionsApi: resolver.resolve()) diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 67acdb8..6517f7f 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -19,6 +19,11 @@ kotlin { ).forEach { it.binaries.framework { baseName = "shared" + + // Fix builds in Xcode 15 + if (System.getenv("XCODE_VERSION_MAJOR") == "1500") { + linkerOpts += "-ld64" + } } } diff --git a/shared/src/androidMain/AndroidManifest.xml b/shared/src/androidMain/AndroidManifest.xml index 568741e..f0f34af 100644 --- a/shared/src/androidMain/AndroidManifest.xml +++ b/shared/src/androidMain/AndroidManifest.xml @@ -1,2 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest />
\ No newline at end of file +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> +</manifest>
\ No newline at end of file diff --git a/shared/src/androidMain/kotlin/mx/trackermap/TrackerMap/controllers/NetworkController.kt b/shared/src/androidMain/kotlin/mx/trackermap/TrackerMap/controllers/NetworkController.kt new file mode 100644 index 0000000..eecd7de --- /dev/null +++ b/shared/src/androidMain/kotlin/mx/trackermap/TrackerMap/controllers/NetworkController.kt @@ -0,0 +1,55 @@ +package mx.trackermap.TrackerMap.controllers + +import android.content.Context +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import android.os.Build +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import mx.trackermap.TrackerMap.Injectable + +actual class NetworkController(context: Context): Injectable { + private val networkRequest = NetworkRequest.Builder().build() + private val connectivityManager: ConnectivityManager + private val _networkAvailable = MutableStateFlow<Boolean?>(null) + actual val networkAvailable = _networkAvailable.asStateFlow() + + private val networkCallback = object: ConnectivityManager.NetworkCallback() { + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities + ) { + super.onCapabilitiesChanged(network, networkCapabilities) + _networkAvailable.value = checkNetworkAccess(networkCapabilities) + } + + override fun onLost(network: Network) { + super.onLost(network) + _networkAvailable.value = false + } + } + + init { + connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + _networkAvailable.value = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + checkNetworkAccess(connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)) + } else { + connectivityManager.activeNetworkInfo?.isConnected == true + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + connectivityManager.registerDefaultNetworkCallback(networkCallback) + } else { + connectivityManager.registerNetworkCallback(networkRequest, networkCallback) + } + } + + private fun checkNetworkAccess(capabilities: NetworkCapabilities?) = + capabilities != null + && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + } else true +}
\ No newline at end of file diff --git a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/infrastructure/ApiClient.kt b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/infrastructure/ApiClient.kt index 937b2dd..8238f7e 100644 --- a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/infrastructure/ApiClient.kt +++ b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/infrastructure/ApiClient.kt @@ -168,7 +168,7 @@ open class ApiClient( } } - if (sessionManager.token.isNotEmpty()) { + if (sessionManager.hasSession) { request.headers["Cookie"] = sessionManager.token } val response: HttpResponse = client.request(request) diff --git a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/infrastructure/SessionManager.kt b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/infrastructure/SessionManager.kt index caf2da1..71ae5d0 100644 --- a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/infrastructure/SessionManager.kt +++ b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/infrastructure/SessionManager.kt @@ -28,6 +28,8 @@ class SessionManager( settings[ACCESS_TOKEN_KEY] = token } + val hasSession: Boolean get() = settings.hasKey(ACCESS_TOKEN_KEY) + fun clearSession() { settings.remove(ACCESS_TOKEN_KEY) settings.remove(SERVER_URL_KEY) diff --git a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/UnitInformation.kt b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/UnitInformation.kt index 6afa350..33e85ee 100644 --- a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/UnitInformation.kt +++ b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/client/models/UnitInformation.kt @@ -53,5 +53,15 @@ data class UnitInformation( } ?: EngineStop.UNKNOWN } else EngineStop.UNKNOWN - fun getHourmeter() = position?.attributes?.get("hours")?.longOrNull + fun getHourmeter() = position?.attributes?.let { attrs -> + if ("io16" in attrs) { + // Minutes + attrs["io16"]?.longOrNull?.let { it * 60 * 1000 } + } else if ("hours" in attrs) { + // Milliseconds + attrs["hours"]?.longOrNull + } else { + null + } + } }
\ No newline at end of file diff --git a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/NetworkController.kt b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/NetworkController.kt new file mode 100644 index 0000000..08dcc87 --- /dev/null +++ b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/NetworkController.kt @@ -0,0 +1,8 @@ +package mx.trackermap.TrackerMap.controllers + +import kotlinx.coroutines.flow.StateFlow +import mx.trackermap.TrackerMap.Injectable + +expect class NetworkController: Injectable { + val networkAvailable: StateFlow<Boolean?> +}
\ No newline at end of file diff --git a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/SessionController.kt b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/SessionController.kt index a63bba2..5cfdf96 100644 --- a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/SessionController.kt +++ b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/SessionController.kt @@ -25,13 +25,15 @@ import kotlinx.serialization.json.JsonPrimitive import mx.trackermap.TrackerMap.Injectable import mx.trackermap.TrackerMap.client.apis.SessionApi import mx.trackermap.TrackerMap.client.apis.UsersApi +import mx.trackermap.TrackerMap.client.infrastructure.SessionManager import mx.trackermap.TrackerMap.client.models.SessionBody import mx.trackermap.TrackerMap.client.models.User @DelicateCoroutinesApi class SessionController( + private val sessionManager: SessionManager, private val sessionApi: SessionApi, - private val usersApi: UsersApi + private val usersApi: UsersApi, ): Injectable { sealed class LoginState { object Nothing: LoginState() @@ -46,6 +48,7 @@ class SessionController( val loginStateFlow = MutableStateFlow<LoginState?>(null) val userFlow = MutableStateFlow<User?>(null) + val hasSession: Boolean get() = sessionManager.hasSession fun getSession() { loginStateFlow.value = LoginState.Loading @@ -59,19 +62,6 @@ class SessionController( } } - fun restoreSession() { - loginStateFlow.value = LoginState.Loading - GlobalScope.launch { - try { - userFlow.value = sessionApi.sessionGet() - loginStateFlow.value = LoginState.Success - } catch (e: Exception) { - e.printStackTrace() - loginStateFlow.value = LoginState.Nothing - } - } - } - fun login(body: SessionBody) { val url = body.url.trim() val email = body.email.trim() diff --git a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/utils/ReportDates.kt b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/utils/ReportDates.kt index 5298df3..9f3a142 100644 --- a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/utils/ReportDates.kt +++ b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/utils/ReportDates.kt @@ -22,6 +22,8 @@ import kotlinx.datetime.* @DelicateCoroutinesApi class ReportDates { + + // Don't remove! Used by iOS enum class PeriodTypes { TODAY, LAST_24, @@ -35,7 +37,7 @@ class ReportDates { sealed class ReportPeriod { val timezone = TimeZone.currentSystemDefault() - val clock = Clock.System + private val clock = Clock.System val instant = clock.now() val dateTime = instant.toLocalDateTime(timezone) val date = dateTime.date @@ -47,7 +49,7 @@ class ReportDates { return formatDateTime(from) to formatDateTime(to) } - fun formatDateTime(dateTime: LocalDateTime) = + private fun formatDateTime(dateTime: LocalDateTime) = dateTime.toInstant(timezone).toString() class Today : ReportPeriod() { diff --git a/shared/src/iosMain/kotlin/mx/trackermap/TrackerMap/controllers/NetworkController.kt b/shared/src/iosMain/kotlin/mx/trackermap/TrackerMap/controllers/NetworkController.kt new file mode 100644 index 0000000..d112c11 --- /dev/null +++ b/shared/src/iosMain/kotlin/mx/trackermap/TrackerMap/controllers/NetworkController.kt @@ -0,0 +1,25 @@ +package mx.trackermap.TrackerMap.controllers + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import mx.trackermap.TrackerMap.Injectable +import platform.Network.* +import platform.darwin.* + +actual class NetworkController: Injectable { + private val monitor = nw_path_monitor_create() + private val queue: dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND.toLong(), 0u) + private val _networkAvailable = MutableStateFlow<Boolean?>(null) + actual val networkAvailable = _networkAvailable.asStateFlow() + + private val updateHandler: nw_path_monitor_update_handler_t = { path: nw_path_t -> + val status = nw_path_get_status(path) + _networkAvailable.value = status in arrayOf(nw_path_status_satisfied, nw_path_status_satisfiable) + } + + init { + nw_path_monitor_set_update_handler(monitor, updateHandler) + nw_path_monitor_set_queue(monitor, queue) + nw_path_monitor_start(monitor) + } +}
\ No newline at end of file |