Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1795] Add status view about internet connection #1839

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's the need for a connectivity repository, as repositories have a different purpose than this. I'd suggest putting this in an utility class as in the end it's just a static method to which you can pass the application context


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()

Comment on lines +54 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd avoid to have 2 function calls semantically so close, so you can call that viewModel.observeInternetConnection in the observeInternetConnection method

// 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps I'd refactor this to isConnectedToInternet


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)
}
}
Loading