-
Notifications
You must be signed in to change notification settings - Fork 8
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
---|---|---|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
// Load all the json schemas in background when the app is started. | ||
GlobalScope.launch { | ||
loadSchema(JsonUtils.ROOT_SCHEMA) | ||
|
@@ -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) } | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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 */ | ||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps I'd refactor this to |
||
|
||
val isWitnessingEnabled = MutableLiveData(java.lang.Boolean.FALSE) | ||
|
||
/** | ||
|
@@ -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. | ||
* | ||
|
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> |
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) | ||
} | ||
} |
There was a problem hiding this comment.
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