aboutsummaryrefslogtreecommitdiff
path: root/androidApp
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2023-09-17 21:56:55 -0600
committerIván Ávalos <avalos@disroot.org>2023-09-17 23:51:33 -0600
commitedbd2c7713a0ba4e7e7a3ba6d59d16861ea4eb23 (patch)
tree885ca095c993c7a661303d215d9be0a6271ba3ea /androidApp
parent7aec305729b872d668df45eae4821b106c1a20cb (diff)
downloadetbsa-trackermap-mobile-edbd2c7713a0ba4e7e7a3ba6d59d16861ea4eb23.tar.gz
etbsa-trackermap-mobile-edbd2c7713a0ba4e7e7a3ba6d59d16861ea4eb23.tar.bz2
etbsa-trackermap-mobile-edbd2c7713a0ba4e7e7a3ba6d59d16861ea4eb23.zip
- [shared] Implement network state monitoring
- [android] UI reacts to network state - [ios] UI reacts to network state
Diffstat (limited to 'androidApp')
-rw-r--r--androidApp/src/main/java/mx/trackermap/TrackerMap/android/TrackerApp.kt4
-rw-r--r--androidApp/src/main/java/mx/trackermap/TrackerMap/android/session/LoginFragment.kt23
-rw-r--r--androidApp/src/main/java/mx/trackermap/TrackerMap/android/session/LoginViewModel.kt20
-rw-r--r--androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsActivity.kt5
-rw-r--r--androidApp/src/main/java/mx/trackermap/TrackerMap/android/units/UnitsViewModel.kt14
-rw-r--r--androidApp/src/main/res/layout/login.xml247
-rw-r--r--androidApp/src/main/res/layout/offline_banner.xml15
-rw-r--r--androidApp/src/main/res/layout/units_activity.xml17
-rw-r--r--androidApp/src/main/res/values-es-rMX/strings.xml1
-rw-r--r--androidApp/src/main/res/values/strings.xml1
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>