Skip to content

Commit

Permalink
Add status view about internet connection
Browse files Browse the repository at this point in the history
  • Loading branch information
WentuM committed Jun 2, 2024
1 parent 0252ce0 commit 9b0ac6f
Show file tree
Hide file tree
Showing 12 changed files with 254 additions and 0 deletions.
1 change: 1 addition & 0 deletions fe2-android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
android:name=".PoPApplication"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.github.dedis.popstellar.repository

import android.app.Application
import android.content.Context
import android.net.ConnectivityManager
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ConnectivityRepository @Inject constructor(application: Application) {

private val connectivityManager =
application.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE)
as ConnectivityManager

fun observeConnectivity(): Observable<Boolean> {
return Observable.create<Boolean> { emitter ->
val callback =
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: android.net.Network) {
emitter.onNext(true)
}

override fun onLost(network: android.net.Network) {
emitter.onNext(false)
}
}

connectivityManager.registerDefaultNetworkCallback(callback)

emitter.setCancellable { connectivityManager.unregisterNetworkCallback(callback) }
}
.subscribeOn(Schedulers.io())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.github.dedis.popstellar.ui

import android.content.Context
import android.transition.Slide
import android.transition.TransitionManager
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.isVisible
import com.github.dedis.popstellar.databinding.NetworkStatusBinding

class NetworkStatusView
@JvmOverloads
constructor(
context: Context,
attrs: AttributeSet,
defStyleAttr: Int = 0,
defStyleRes: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {

private val binding = NetworkStatusBinding.inflate(LayoutInflater.from(context), this, true)

fun setIsNetworkConnected(isVisible: Boolean) {
setVisibility(isVisible)
}

private fun setVisibility(isVisible: Boolean) {
TransitionManager.beginDelayedTransition(
this.parent as ViewGroup,
Slide(Gravity.TOP).apply {
addTarget(binding.networkConnectionContainer)
duration = VISIBILITY_CHANGE_DURATION
})
binding.networkConnectionContainer.isVisible = !isVisible
}

companion object {
private const val VISIBILITY_CHANGE_DURATION = 400L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,12 @@ class HomeActivity : AppCompatActivity() {
// When back to the home activity set connecting in view model to false
viewModel.disableConnectingFlag()

viewModel.observeInternetConnection()

handleTopAppBar()

observeInternetConnection()

// Load all the json schemas in background when the app is started.
GlobalScope.launch {
loadSchema(JsonUtils.ROOT_SCHEMA)
Expand All @@ -77,6 +81,12 @@ class HomeActivity : AppCompatActivity() {
}
}

private fun observeInternetConnection() {
viewModel.isInternetConnected.observe(this) {
binding.networkStatusView.setIsNetworkConnected(it)
}
}

private fun handleTopAppBar() {
viewModel.pageTitle.observe(this) { resId: Int -> binding.topAppBar.setTitle(resId) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import com.github.dedis.popstellar.model.objects.Wallet
import com.github.dedis.popstellar.model.objects.view.LaoView
import com.github.dedis.popstellar.model.qrcode.ConnectToLao
import com.github.dedis.popstellar.model.qrcode.ConnectToLao.Companion.extractFrom
import com.github.dedis.popstellar.repository.ConnectivityRepository
import com.github.dedis.popstellar.repository.LAORepository
import com.github.dedis.popstellar.repository.database.AppDatabase
import com.github.dedis.popstellar.repository.remote.GlobalNetworkManager
import com.github.dedis.popstellar.ui.PopViewModel
import com.github.dedis.popstellar.ui.lao.LaoViewModel
import com.github.dedis.popstellar.ui.qrcode.QRCodeScanningViewModel
import com.github.dedis.popstellar.utility.ActivityUtils.saveWalletRoutine
import com.github.dedis.popstellar.utility.error.ErrorUtils.logAndShow
Expand All @@ -24,6 +26,7 @@ import com.google.gson.Gson
import com.google.gson.JsonParseException
import dagger.hilt.android.lifecycle.HiltViewModel
import io.reactivex.BackpressureStrategy
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import java.security.GeneralSecurityException
Expand All @@ -42,6 +45,7 @@ constructor(
private val wallet: Wallet,
private val laoRepository: LAORepository,
private val networkManager: GlobalNetworkManager,
private val connectivityRepository: ConnectivityRepository,
private val appDatabase: AppDatabase
) : AndroidViewModel(application), QRCodeScanningViewModel, PopViewModel {
/** LiveData objects that represent the state in a fragment */
Expand All @@ -56,6 +60,8 @@ constructor(
/** This LiveData boolean is used to indicate whether the HomeFragment is displayed */
val isHome = MutableLiveData(java.lang.Boolean.TRUE)

val isInternetConnected = MutableLiveData(java.lang.Boolean.TRUE)

val isWitnessingEnabled = MutableLiveData(java.lang.Boolean.FALSE)

/**
Expand Down Expand Up @@ -190,6 +196,18 @@ constructor(
}
}

fun observeInternetConnection() {
addDisposable(
connectivityRepository
.observeConnectivity()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ isConnected -> isInternetConnected.value = isConnected },
{ error: Throwable ->
Timber.tag(LaoViewModel.TAG).e(error, "error connection status")
}))
}

/**
* Function to set the liveData isWitnessingEnabled.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class LaoActivity : AppCompatActivity() {
laoViewModel.laoId = laoId
laoViewModel.observeLao(laoId)
laoViewModel.observeRollCalls(laoId)
laoViewModel.observeInternetConnection()

witnessingViewModel = obtainWitnessingViewModel(this, laoId)

Expand All @@ -78,6 +79,7 @@ class LaoActivity : AppCompatActivity() {

observeRoles()
observeToolBar()
observeInternetConnection()
observeDrawer()
setupDrawerHeader()
observeWitnessPopup()
Expand Down Expand Up @@ -110,6 +112,12 @@ class LaoActivity : AppCompatActivity() {
laoViewModel.role.observe(this) { role: Role -> setupHeaderRole(role) }
}

private fun observeInternetConnection() {
laoViewModel.isInternetConnected.observe(this) {
binding.networkStatusView.setIsNetworkConnected(it)
}
}

private fun observeToolBar() {
// Listen to click on left icon of toolbar
binding.laoAppBar.setNavigationOnClickListener {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.github.dedis.popstellar.model.objects.Wallet
import com.github.dedis.popstellar.model.objects.security.PoPToken
import com.github.dedis.popstellar.model.objects.security.PublicKey
import com.github.dedis.popstellar.model.objects.view.LaoView
import com.github.dedis.popstellar.repository.ConnectivityRepository
import com.github.dedis.popstellar.repository.LAORepository
import com.github.dedis.popstellar.repository.RollCallRepository
import com.github.dedis.popstellar.repository.WitnessingRepository
Expand All @@ -32,6 +33,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import javax.inject.Inject
import kotlinx.coroutines.flow.observeOn
import timber.log.Timber

@HiltViewModel
Expand All @@ -46,6 +48,7 @@ constructor(
private val laoRepo: LAORepository,
private val rollCallRepo: RollCallRepository,
private val witnessingRepo: WitnessingRepository,
private val connectivityRepository: ConnectivityRepository,
private val networkManager: GlobalNetworkManager,
private val keyManager: KeyManager,
private val wallet: Wallet,
Expand All @@ -65,6 +68,8 @@ constructor(
val isAttendee = MutableLiveData(java.lang.Boolean.FALSE)
val role = MutableLiveData(Role.MEMBER)

val isInternetConnected = MutableLiveData(java.lang.Boolean.TRUE)

private val disposables = CompositeDisposable()
private val subscriptionsDao: SubscriptionsDao = appDatabase.subscriptionsDao()

Expand Down Expand Up @@ -219,6 +224,16 @@ constructor(
}))
}

fun observeInternetConnection() {
addDisposable(
connectivityRepository
.observeConnectivity()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ isConnected -> isInternetConnected.value = isConnected },
{ error: Throwable -> Timber.tag(TAG).e(error, "error connection status") }))
}

private fun isRollCallAttended(rollcall: RollCall, laoId: String): Boolean {
return try {
val pk = wallet.generatePoPToken(laoId, rollcall.persistentId).publicKey
Expand Down
7 changes: 7 additions & 0 deletions fe2-android/app/src/main/res/layout/home_activity.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
app:navigationIcon="@drawable/home_icon" />
</com.google.android.material.appbar.AppBarLayout>

<com.github.dedis.popstellar.ui.NetworkStatusView
android:id="@+id/networkStatusView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/home_bar_layout"
app:layout_constraintStart_toStartOf="parent"/>

<FrameLayout
android:id="@+id/fragment_container_home"
android:layout_width="match_parent"
Expand Down
5 changes: 5 additions & 0 deletions fe2-android/app/src/main/res/layout/lao_activity.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
app:navigationIcon="@drawable/menu_icon" />
</com.google.android.material.appbar.AppBarLayout>

<com.github.dedis.popstellar.ui.NetworkStatusView
android:id="@+id/networkStatusView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<FrameLayout
android:id="@+id/fragment_container_lao"
android:layout_width="match_parent"
Expand Down
21 changes: 21 additions & 0 deletions fe2-android/app/src/main/res/layout/network_status.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

<FrameLayout
android:id="@+id/networkConnectionContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="2dp"
android:background="@color/background_gray">

<TextView
android:id="@+id/statusTextView"
style="@style/text_high_emphasis"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/network_status_connection_fail" />

</FrameLayout>

</layout>
2 changes: 2 additions & 0 deletions fe2-android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -375,4 +375,6 @@
<string name="invalid_qrcode_lao_data">Invalid QRCode laoData</string>
<string name="invalid_qrcode_popcha_data">Invalid URL</string>

<string name="network_status_connection_fail">No internet connection</string>

</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.github.dedis.popstellar.repository

import android.app.Application
import android.content.Context
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import io.reactivex.Observable
import io.reactivex.observers.TestObserver
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.*
import org.mockito.Mockito.*
import org.mockito.junit.MockitoJUnitRunner


@RunWith(MockitoJUnitRunner::class)
class ConnectivityRepositoryTest {
@Mock
private lateinit var application: Application

@Mock
private lateinit var context: Context

@Mock
lateinit var connectivityManager: ConnectivityManager

private lateinit var connectivityRepository: ConnectivityRepository

@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
`when`(application.applicationContext).thenReturn(context)
`when`(context.getSystemService(Context.CONNECTIVITY_SERVICE))
.thenReturn(connectivityManager)
connectivityRepository = ConnectivityRepository(application)
}

@Test
fun testObserveConnectivity_onAvailable() {
val callbackCaptor = ArgumentCaptor.forClass(
NetworkCallback::class.java
)

val observable: Observable<Boolean> = connectivityRepository.observeConnectivity()
val testObserver: TestObserver<Boolean> = observable.test()

verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
val callback = callbackCaptor.value

callback.onAvailable(mock(Network::class.java))
testObserver.assertValue(true)
}

@Test
fun testObserveConnectivity_onLost() {
val callbackCaptor = ArgumentCaptor.forClass(
NetworkCallback::class.java
)

val observable = connectivityRepository.observeConnectivity()
val testObserver = observable.test()

verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
val callback = callbackCaptor.value

callback.onLost(mock(Network::class.java))
testObserver.assertValue(false)
}

@Test
fun testObserveConnectivity_cancellable() {
val callbackCaptor = ArgumentCaptor.forClass(
NetworkCallback::class.java
)

val observable = connectivityRepository.observeConnectivity()
val testObserver = observable.test()

verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
val callback = callbackCaptor.value

testObserver.dispose()
verify(connectivityManager).unregisterNetworkCallback(callback)
}
}

0 comments on commit 9b0ac6f

Please sign in to comment.