mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +03:00
Merge pull request #560 from vector-im/feature/no_network
Display a "No network" banner when the device has no network
This commit is contained in:
commit
4453f0ced9
13 changed files with 145 additions and 91 deletions
|
@ -7,6 +7,7 @@ Features:
|
|||
|
||||
Improvements:
|
||||
- Reduce default release build log level, and lab option to enable more logs.
|
||||
- Display a no network indicator when there is no network (#559)
|
||||
|
||||
Other changes:
|
||||
-
|
||||
|
|
|
@ -22,4 +22,5 @@ sealed class SyncState {
|
|||
object PAUSED : SyncState()
|
||||
object KILLING : SyncState()
|
||||
object KILLED : SyncState()
|
||||
object NO_NETWORK : SyncState()
|
||||
}
|
|
@ -18,12 +18,10 @@ package im.vector.matrix.android.internal.network
|
|||
|
||||
import android.content.Context
|
||||
import com.novoda.merlin.Merlin
|
||||
import com.novoda.merlin.MerlinsBeard
|
||||
import im.vector.matrix.android.internal.di.MatrixScope
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
@ -35,29 +33,38 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context)
|
|||
.withDisconnectableCallbacks()
|
||||
.build(context)
|
||||
|
||||
private val merlinsBeard = MerlinsBeard.Builder().build(context)
|
||||
private val listeners = Collections.synchronizedList(ArrayList<Listener>())
|
||||
private val listeners = Collections.synchronizedSet(LinkedHashSet<Listener>())
|
||||
|
||||
// True when internet is available
|
||||
var hasInternetAccess = false
|
||||
private set
|
||||
|
||||
init {
|
||||
merlin.bind()
|
||||
merlin.registerDisconnectable {
|
||||
Timber.v("On Disconnect")
|
||||
val localListeners = listeners.toList()
|
||||
localListeners.forEach {
|
||||
it.onDisconnect()
|
||||
if (hasInternetAccess) {
|
||||
Timber.v("On Disconnect")
|
||||
hasInternetAccess = false
|
||||
val localListeners = listeners.toList()
|
||||
localListeners.forEach {
|
||||
it.onDisconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
merlin.registerConnectable {
|
||||
Timber.v("On Connect")
|
||||
val localListeners = listeners.toList()
|
||||
localListeners.forEach {
|
||||
it.onConnect()
|
||||
if (!hasInternetAccess) {
|
||||
Timber.v("On Connect")
|
||||
hasInternetAccess = true
|
||||
val localListeners = listeners.toList()
|
||||
localListeners.forEach {
|
||||
it.onConnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun waitUntilConnected() {
|
||||
if (isConnected()) {
|
||||
if (hasInternetAccess) {
|
||||
return
|
||||
} else {
|
||||
suspendCoroutine<Unit> { continuation ->
|
||||
|
@ -79,10 +86,6 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context)
|
|||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
fun isConnected(): Boolean {
|
||||
return merlinsBeard.isConnected
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onConnect() {
|
||||
|
||||
|
|
|
@ -93,8 +93,8 @@ open class SyncService : Service() {
|
|||
}
|
||||
|
||||
fun doSync(once: Boolean = false) {
|
||||
if (!networkConnectivityChecker.isConnected()) {
|
||||
Timber.v("Sync is Paused. Waiting...")
|
||||
if (!networkConnectivityChecker.hasInternetAccess) {
|
||||
Timber.v("No internet access. Waiting...")
|
||||
//TODO Retry in ?
|
||||
timer.schedule(object : TimerTask() {
|
||||
override fun run() {
|
||||
|
|
|
@ -50,6 +50,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
|||
private val lock = Object()
|
||||
private var cancelableTask: Cancelable? = null
|
||||
|
||||
private var isStarted = false
|
||||
|
||||
init {
|
||||
updateStateTo(SyncState.IDLE)
|
||||
}
|
||||
|
@ -60,19 +62,18 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
|||
}
|
||||
|
||||
fun restart() = synchronized(lock) {
|
||||
if (state is SyncState.PAUSED) {
|
||||
if (!isStarted) {
|
||||
Timber.v("Resume sync...")
|
||||
updateStateTo(SyncState.RUNNING(afterPause = true))
|
||||
isStarted = true
|
||||
lock.notify()
|
||||
}
|
||||
}
|
||||
|
||||
fun pause() = synchronized(lock) {
|
||||
if (state is SyncState.RUNNING) {
|
||||
if (isStarted) {
|
||||
Timber.v("Pause sync...")
|
||||
updateStateTo(SyncState.PAUSED)
|
||||
isStarted = false
|
||||
cancelableTask?.cancel()
|
||||
lock.notify()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,19 +88,31 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
|||
return liveState
|
||||
}
|
||||
|
||||
override fun onConnect() {
|
||||
Timber.v("Network is back")
|
||||
synchronized(lock) {
|
||||
lock.notify()
|
||||
}
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
Timber.v("Start syncing...")
|
||||
isStarted = true
|
||||
networkConnectivityChecker.register(this)
|
||||
backgroundDetectionObserver.register(this)
|
||||
|
||||
while (state != SyncState.KILLING) {
|
||||
Timber.v("Entering loop, state: $state")
|
||||
|
||||
if (!networkConnectivityChecker.isConnected() || state == SyncState.PAUSED) {
|
||||
Timber.v("No network or sync is Paused. Waiting...")
|
||||
synchronized(lock) {
|
||||
lock.wait()
|
||||
}
|
||||
if (!networkConnectivityChecker.hasInternetAccess) {
|
||||
Timber.v("No network. Waiting...")
|
||||
updateStateTo(SyncState.NO_NETWORK)
|
||||
synchronized(lock) { lock.wait() }
|
||||
Timber.v("...unlocked")
|
||||
} else if (!isStarted) {
|
||||
Timber.v("Sync is Paused. Waiting...")
|
||||
updateStateTo(SyncState.PAUSED)
|
||||
synchronized(lock) { lock.wait() }
|
||||
Timber.v("...unlocked")
|
||||
} else {
|
||||
if (state !is SyncState.RUNNING) {
|
||||
|
@ -167,16 +180,11 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
|||
}
|
||||
|
||||
private fun updateStateTo(newState: SyncState) {
|
||||
Timber.v("Update state to $newState")
|
||||
Timber.v("Update state from $state to $newState")
|
||||
state = newState
|
||||
liveState.postValue(newState)
|
||||
}
|
||||
|
||||
override fun onConnect() {
|
||||
synchronized(lock) {
|
||||
lock.notify()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMoveToForeground() {
|
||||
restart()
|
||||
|
|
|
@ -33,7 +33,7 @@ internal class BackgroundDetectionObserver @Inject constructor() : LifecycleObse
|
|||
private set
|
||||
|
||||
private
|
||||
val listeners = ArrayList<Listener>()
|
||||
val listeners = LinkedHashSet<Listener>()
|
||||
|
||||
fun register(listener: Listener) {
|
||||
listeners.add(listener)
|
||||
|
|
|
@ -3,4 +3,7 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
<string name="no_network_indicator">There is no network connection right now</string>
|
||||
</resources>
|
|
@ -19,7 +19,6 @@ package im.vector.riotx.features.home
|
|||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.core.view.forEachIndexed
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
|
@ -30,7 +29,6 @@ import com.google.android.material.bottomnavigation.BottomNavigationItemView
|
|||
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.platform.ToolbarConfigurable
|
||||
|
@ -208,11 +206,7 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate {
|
|||
unreadCounterBadgeViews[INDEX_CATCHUP].render(UnreadCounterBadgeView.State(it.notificationCountCatchup, it.notificationHighlightCatchup))
|
||||
unreadCounterBadgeViews[INDEX_PEOPLE].render(UnreadCounterBadgeView.State(it.notificationCountPeople, it.notificationHighlightPeople))
|
||||
unreadCounterBadgeViews[INDEX_ROOMS].render(UnreadCounterBadgeView.State(it.notificationCountRooms, it.notificationHighlightRooms))
|
||||
syncProgressBar.visibility = when (it.syncState) {
|
||||
is SyncState.RUNNING -> if (it.syncState.afterPause) View.VISIBLE else View.GONE
|
||||
else -> View.GONE
|
||||
}
|
||||
syncProgressBarWrap.visibility = syncProgressBar.visibility
|
||||
syncStateView.render(it.syncState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -65,7 +65,6 @@ import im.vector.matrix.android.api.session.room.send.SendState
|
|||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
|
@ -77,8 +76,8 @@ import im.vector.riotx.core.extensions.observeEvent
|
|||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
import im.vector.riotx.core.files.addEntryToDownloadManager
|
||||
import im.vector.riotx.core.glide.GlideApp
|
||||
import im.vector.riotx.core.ui.views.NotificationAreaView
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.core.ui.views.NotificationAreaView
|
||||
import im.vector.riotx.core.utils.*
|
||||
import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter
|
||||
import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
|
||||
|
@ -251,11 +250,7 @@ class RoomDetailFragment :
|
|||
}
|
||||
|
||||
roomDetailViewModel.selectSubscribe(RoomDetailViewState::syncState) { syncState ->
|
||||
syncProgressBar.visibility = when (syncState) {
|
||||
is SyncState.RUNNING -> if (syncState.afterPause) View.VISIBLE else View.GONE
|
||||
else -> View.GONE
|
||||
}
|
||||
syncProgressBarWrap.visibility = syncProgressBar.visibility
|
||||
syncStateView.render(syncState)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
43
vector/src/main/java/im/vector/riotx/features/sync/widget/SyncStateView.kt
Executable file
43
vector/src/main/java/im/vector/riotx/features/sync/widget/SyncStateView.kt
Executable file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.sync.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.isVisible
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.riotx.R
|
||||
import kotlinx.android.synthetic.main.view_sync_state.view.*
|
||||
|
||||
class SyncStateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
|
||||
: FrameLayout(context, attrs, defStyle) {
|
||||
|
||||
init {
|
||||
View.inflate(context, R.layout.view_sync_state, this)
|
||||
}
|
||||
|
||||
|
||||
fun render(newState: SyncState) {
|
||||
syncStateProgressBar.visibility = when (newState) {
|
||||
is SyncState.RUNNING -> if (newState.afterPause) View.VISIBLE else View.GONE
|
||||
else -> View.GONE
|
||||
}
|
||||
syncStateNoNetwork.isVisible = newState == SyncState.NO_NETWORK
|
||||
}
|
||||
}
|
|
@ -44,29 +44,13 @@
|
|||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<!-- Trick to remove surrounding padding (clip frome wrapping frame) -->
|
||||
<FrameLayout
|
||||
android:id="@+id/syncProgressBarWrap"
|
||||
<im.vector.riotx.features.sync.widget.SyncStateView
|
||||
android:id="@+id/syncStateView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="3dp"
|
||||
android:visibility="gone"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/groupToolbar"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/syncProgressBar"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="14dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="?riotx_header_panel_background"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
app:layout_constraintTop_toBottomOf="@id/groupToolbar" />
|
||||
|
||||
<im.vector.riotx.core.ui.views.KeysBackupBanner
|
||||
android:id="@+id/homeKeysBackupBanner"
|
||||
|
@ -76,7 +60,7 @@
|
|||
android:minHeight="67dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/syncProgressBarWrap" />
|
||||
app:layout_constraintTop_toBottomOf="@id/syncStateView" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/roomListContainer"
|
||||
|
|
|
@ -71,28 +71,13 @@
|
|||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<!-- Trick to remove surrounding padding (clip frome wrapping frame) -->
|
||||
<FrameLayout
|
||||
android:id="@+id/syncProgressBarWrap"
|
||||
<im.vector.riotx.features.sync.widget.SyncStateView
|
||||
android:id="@+id/syncStateView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="3dp"
|
||||
android:visibility="gone"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/roomToolbar"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/syncProgressBar"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="14dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="?riotx_header_panel_background"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
</FrameLayout>
|
||||
app:layout_constraintTop_toBottomOf="@id/roomToolbar" />
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
|
@ -102,7 +87,7 @@
|
|||
app:layout_constraintBottom_toTopOf="@+id/recyclerViewBarrier"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/syncProgressBarWrap"
|
||||
app:layout_constraintTop_toBottomOf="@id/syncStateView"
|
||||
tools:listitem="@layout/item_timeline_event_base" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
|
|
37
vector/src/main/res/layout/view_sync_state.xml
Normal file
37
vector/src/main/res/layout/view_sync_state.xml
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:orientation="vertical"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
|
||||
<!-- Trick to remove surrounding padding (clip from wrapping frame) -->
|
||||
<FrameLayout
|
||||
android:id="@+id/syncStateProgressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="3dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ProgressBar
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="14dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="?riotx_header_panel_background"
|
||||
android:indeterminate="true" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/syncStateNoNetwork"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/vector_warning_color"
|
||||
android:gravity="center"
|
||||
android:text="@string/no_network_indicator"
|
||||
android:textColor="@color/white"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</merge>
|
Loading…
Reference in a new issue