diff options
Diffstat (limited to 'androidApp')
10 files changed, 222 insertions, 125 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/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/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/layout/login.xml b/androidApp/src/main/res/layout/login.xml index 78c3e46..816e625 100644 --- a/androidApp/src/main/res/layout/login.xml +++ b/androidApp/src/main/res/layout/login.xml @@ -1,131 +1,150 @@ <?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"> - - <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"> + + <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 912a5d9..59e0408 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/strings.xml b/androidApp/src/main/res/values/strings.xml index 57eaf66..93cda7f 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> |