diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 23ec462..ab23cb3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,6 +19,12 @@
+
+
+
+
+
+
diff --git a/app/src/main/java/pro/averin/anton/clean/android/cookbook/data/common/connection/ConnectionChecker.kt b/app/src/main/java/pro/averin/anton/clean/android/cookbook/data/common/connection/ConnectionChecker.kt
new file mode 100644
index 0000000..142d9e2
--- /dev/null
+++ b/app/src/main/java/pro/averin/anton/clean/android/cookbook/data/common/connection/ConnectionChecker.kt
@@ -0,0 +1,38 @@
+package pro.averin.anton.clean.android.cookbook.data.common.connection
+
+import android.net.ConnectivityManager
+import javax.inject.Inject
+import javax.inject.Singleton
+
+
+@Singleton
+class ConnectionChecker @Inject constructor() {
+
+ @Inject lateinit var connectivityManager: ConnectivityManager
+
+ fun getNetworkStatus(): Int {
+ val networkInfo = connectivityManager.activeNetworkInfo
+ if (networkInfo != null && networkInfo.isConnectedOrConnecting) {
+ when (networkInfo.type) {
+ ConnectivityManager.TYPE_WIFI -> return WIFI
+ ConnectivityManager.TYPE_MOBILE -> return MOBILE
+ else -> return OFFLINE
+ }
+ }
+
+ return OFFLINE;
+ }
+
+ fun isOnline(): Boolean {
+ val activeNetwork = connectivityManager.activeNetworkInfo
+
+ return activeNetwork != null && activeNetwork.isConnectedOrConnecting
+ }
+
+
+ companion object NetworkStatus {
+ val OFFLINE = 1
+ val MOBILE = 2
+ val WIFI = 3
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/pro/averin/anton/clean/android/cookbook/di/AppComponent.kt b/app/src/main/java/pro/averin/anton/clean/android/cookbook/di/AppComponent.kt
index 347f293..99fa385 100644
--- a/app/src/main/java/pro/averin/anton/clean/android/cookbook/di/AppComponent.kt
+++ b/app/src/main/java/pro/averin/anton/clean/android/cookbook/di/AppComponent.kt
@@ -1,11 +1,14 @@
package pro.averin.anton.clean.android.cookbook.di
+import android.net.ConnectivityManager
import com.tbruyelle.rxpermissions.RxPermissions
import dagger.Component
import pro.averin.anton.clean.android.cookbook.BaseContext
+import pro.averin.anton.clean.android.cookbook.data.common.connection.ConnectionChecker
import pro.averin.anton.clean.android.cookbook.data.common.rx.Schedulers
import pro.averin.anton.clean.android.cookbook.data.common.rx.bus.GlobalBusSubscriber
import pro.averin.anton.clean.android.cookbook.data.flickr.FlickrRepo
+import pro.averin.anton.clean.android.cookbook.ui.common.broadcasts.ConnectivityBroadcastReceiver
import pro.averin.anton.clean.android.cookbook.ui.common.map.MapUtils
import javax.inject.Singleton
@@ -27,4 +30,8 @@ interface AppComponent {
fun globalBusSubscriber(): GlobalBusSubscriber
fun mapUtils(): MapUtils
+
+ fun connectionChecker(): ConnectionChecker
+ fun connectivityManager(): ConnectivityManager
+ fun injectTo(connectivityBroadcastReceiver: ConnectivityBroadcastReceiver)
}
\ No newline at end of file
diff --git a/app/src/main/java/pro/averin/anton/clean/android/cookbook/di/SystemServicesModule.kt b/app/src/main/java/pro/averin/anton/clean/android/cookbook/di/SystemServicesModule.kt
index 65f6dc6..1118094 100644
--- a/app/src/main/java/pro/averin/anton/clean/android/cookbook/di/SystemServicesModule.kt
+++ b/app/src/main/java/pro/averin/anton/clean/android/cookbook/di/SystemServicesModule.kt
@@ -3,6 +3,7 @@ package pro.averin.anton.clean.android.cookbook.di
import android.content.Context
import android.content.SharedPreferences
import android.location.LocationManager
+import android.net.ConnectivityManager
import android.preference.PreferenceManager
import dagger.Module
import dagger.Provides
@@ -12,6 +13,12 @@ import javax.inject.Singleton
@Module
class SystemServicesModule(private val baseContext: BaseContext) {
+ @Provides
+ @Singleton
+ fun connectivityManager(): ConnectivityManager {
+ return baseContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ }
+
@Provides
@Singleton
fun locationManager(): LocationManager {
diff --git a/app/src/main/java/pro/averin/anton/clean/android/cookbook/ui/common/ConnectionEventsHandler.kt b/app/src/main/java/pro/averin/anton/clean/android/cookbook/ui/common/ConnectionEventsHandler.kt
new file mode 100644
index 0000000..ebe1761
--- /dev/null
+++ b/app/src/main/java/pro/averin/anton/clean/android/cookbook/ui/common/ConnectionEventsHandler.kt
@@ -0,0 +1,37 @@
+package pro.averin.anton.clean.android.cookbook.ui.common
+
+import pro.averin.anton.clean.android.cookbook.data.common.connection.ConnectionChecker
+import pro.averin.anton.clean.android.cookbook.data.common.rx.bus.GlobalBusSubscriber
+import pro.averin.anton.clean.android.cookbook.data.common.rx.bus.events.ConnectivityAvailableGlobalEvent
+import pro.averin.anton.clean.android.cookbook.data.common.rx.bus.events.ConnectivityUnavailableGlobalEvent
+import pro.averin.anton.clean.android.cookbook.di.ActivityScope
+import pro.averin.anton.clean.android.cookbook.ui.common.resolution.Resolution
+import javax.inject.Inject
+
+@ActivityScope
+class ConnectionEventsHandler @Inject constructor(
+ private val globalBusSubscriber: GlobalBusSubscriber,
+ private val connectionChecker: ConnectionChecker
+) {
+
+ fun start(resolution: Resolution) {
+ globalBusSubscriber.subscribe(ConnectivityAvailableGlobalEvent::class) {
+ resolution.onConnectivityAvailable()
+ }
+ globalBusSubscriber.subscribe(ConnectivityUnavailableGlobalEvent::class) {
+ resolution.onConnectivityUnavailable()
+ }
+
+ if (!connectionChecker.isOnline()) {
+ resolution.onConnectivityUnavailable()
+ } else {
+ resolution.onConnectivityAvailable()
+ }
+
+ }
+
+ fun stop() {
+ globalBusSubscriber.unsubscribe()
+ }
+
+}
diff --git a/app/src/main/java/pro/averin/anton/clean/android/cookbook/ui/common/broadcasts/ConnectivityBroadcastReceiver.kt b/app/src/main/java/pro/averin/anton/clean/android/cookbook/ui/common/broadcasts/ConnectivityBroadcastReceiver.kt
new file mode 100644
index 0000000..34bdc61
--- /dev/null
+++ b/app/src/main/java/pro/averin/anton/clean/android/cookbook/ui/common/broadcasts/ConnectivityBroadcastReceiver.kt
@@ -0,0 +1,28 @@
+package pro.averin.anton.clean.android.cookbook.ui.common.broadcasts
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import pro.averin.anton.clean.android.cookbook.BaseContext
+import pro.averin.anton.clean.android.cookbook.data.common.connection.ConnectionChecker
+import pro.averin.anton.clean.android.cookbook.data.common.rx.bus.GlobalBus
+import pro.averin.anton.clean.android.cookbook.data.common.rx.bus.events.ConnectivityAvailableGlobalEvent
+import pro.averin.anton.clean.android.cookbook.data.common.rx.bus.events.ConnectivityUnavailableGlobalEvent
+import javax.inject.Inject
+
+class ConnectivityBroadcastReceiver : BroadcastReceiver() {
+
+ @Inject lateinit var connectionChecker: ConnectionChecker
+ @Inject lateinit var globalBus: GlobalBus
+
+ override fun onReceive(context: Context?, intent: Intent?) {
+ val baseContext = context?.applicationContext as BaseContext
+ baseContext.appComponent.injectTo(this)
+
+ if (connectionChecker.isOnline()) {
+ globalBus.post(ConnectivityAvailableGlobalEvent())
+ } else {
+ globalBus.post(ConnectivityUnavailableGlobalEvent())
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/pro/averin/anton/clean/android/cookbook/ui/main/presenter/MainPresenter.kt b/app/src/main/java/pro/averin/anton/clean/android/cookbook/ui/main/presenter/MainPresenter.kt
index 5f16403..aacd45b 100644
--- a/app/src/main/java/pro/averin/anton/clean/android/cookbook/ui/main/presenter/MainPresenter.kt
+++ b/app/src/main/java/pro/averin/anton/clean/android/cookbook/ui/main/presenter/MainPresenter.kt
@@ -1,6 +1,8 @@
package pro.averin.anton.clean.android.cookbook.ui.main.presenter
+import android.os.Bundle
import pro.averin.anton.clean.android.cookbook.di.ActivityScope
+import pro.averin.anton.clean.android.cookbook.ui.common.ConnectionEventsHandler
import pro.averin.anton.clean.android.cookbook.ui.common.ExtraLifecycleDelegate
import pro.averin.anton.clean.android.cookbook.ui.common.UINavigator
import pro.averin.anton.clean.android.cookbook.ui.common.presenter.BasePresenter
@@ -11,12 +13,23 @@ import javax.inject.Inject
@ActivityScope
class MainPresenter @Inject constructor(
+ private val connectionEventsHandler: ConnectionEventsHandler
) : BasePresenter(), ExtraLifecycleDelegate, NavigationDrawerViewExtensionDelegate {
@Inject lateinit var uiNavigator: UINavigator
lateinit var navigationDrawerViewExtension: NavigationDrawerViewExtensionContract
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ connectionEventsHandler.start(view?.getResolution()!!)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ connectionEventsHandler.stop()
+ }
+
override fun showInitialScreen() {
uiNavigator.showGoogleMapsScreen()
}
diff --git a/app/src/main/java/pro/averin/anton/clean/android/cookbook/ui/main/view/MainActivity.kt b/app/src/main/java/pro/averin/anton/clean/android/cookbook/ui/main/view/MainActivity.kt
index 53a4977..ea36e32 100644
--- a/app/src/main/java/pro/averin/anton/clean/android/cookbook/ui/main/view/MainActivity.kt
+++ b/app/src/main/java/pro/averin/anton/clean/android/cookbook/ui/main/view/MainActivity.kt
@@ -5,16 +5,19 @@ import android.support.v7.widget.Toolbar
import pro.averin.anton.clean.android.cookbook.R
import pro.averin.anton.clean.android.cookbook.databinding.ActivityMainBinding
import pro.averin.anton.clean.android.cookbook.di.ActivityComponent
+import pro.averin.anton.clean.android.cookbook.ui.common.resolution.Resolution
+import pro.averin.anton.clean.android.cookbook.ui.common.resolution.UIResolution
import pro.averin.anton.clean.android.cookbook.ui.common.view.BaseActivity
-import pro.averin.anton.clean.android.cookbook.ui.common.view.ScreenContract
+import pro.averin.anton.clean.android.cookbook.ui.common.view.ResolvedScreenContract
import pro.averin.anton.clean.android.cookbook.ui.main.presenter.MainPresenter
import javax.inject.Inject
-interface MainScreenContract : ScreenContract
+interface MainScreenContract : ResolvedScreenContract
class MainActivity : BaseActivity(), MainScreenContract {
@Inject lateinit var presenter: MainPresenter
+ @Inject lateinit var uiResolution: UIResolution
@Inject lateinit var navigationDrawerViewExtension: NavigationDrawerViewExtension
override fun doInjections(activityComponent: ActivityComponent) {
@@ -41,4 +44,7 @@ class MainActivity : BaseActivity(), MainScreenContract {
}
}
+ override fun getResolution(): Resolution? {
+ return uiResolution
+ }
}
\ No newline at end of file
diff --git a/app/src/test/java/pro/averin/anton/clean/android/cookbook/data/common/connection/ConnectionCheckerTest.kt b/app/src/test/java/pro/averin/anton/clean/android/cookbook/data/common/connection/ConnectionCheckerTest.kt
new file mode 100644
index 0000000..63a7db8
--- /dev/null
+++ b/app/src/test/java/pro/averin/anton/clean/android/cookbook/data/common/connection/ConnectionCheckerTest.kt
@@ -0,0 +1,102 @@
+package pro.averin.anton.clean.android.cookbook.data.common.connection
+
+import android.net.ConnectivityManager
+import android.net.NetworkInfo
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.InjectMocks
+import org.mockito.Mock
+import org.powermock.modules.junit4.PowerMockRunner
+import pro.averin.anton.clean.android.cookbook.kotlin.test.assertThat
+import pro.averin.anton.clean.android.cookbook.kotlin.test.given
+import pro.averin.anton.clean.android.cookbook.kotlin.test.isEqualTo
+
+@RunWith(PowerMockRunner::class)
+class ConnectionCheckerTest {
+
+ @Mock lateinit var connectivityManager: ConnectivityManager
+ @Mock lateinit var activeNetworkInfo: NetworkInfo
+
+ @InjectMocks lateinit var classToTest: ConnectionChecker
+
+ @Test
+ fun networkStatusOfflineWhenNoNetworksConnected() {
+ // given
+ given(connectivityManager.activeNetworkInfo).willReturn(null)
+
+ // when
+ val status = classToTest.getNetworkStatus()
+
+ // then
+ assertThat(status, isEqualTo(ConnectionChecker.OFFLINE))
+ }
+
+ @Test
+ fun networkStatusOfflineWhenNotWifiOrMobileNetworkConnected() {
+ // given
+ given(connectivityManager.activeNetworkInfo).willReturn(activeNetworkInfo)
+ given(activeNetworkInfo.isConnectedOrConnecting).willReturn(true)
+ given(activeNetworkInfo.type).willReturn(ConnectivityManager.TYPE_DUMMY)
+
+ // when
+ val status = classToTest.getNetworkStatus()
+
+ // then
+ assertThat(status, isEqualTo(ConnectionChecker.OFFLINE))
+ }
+
+ @Test
+ fun networkStatusWifiWhenWifiConnected() {
+ // given
+ given(connectivityManager.activeNetworkInfo).willReturn(activeNetworkInfo)
+ given(activeNetworkInfo.type).willReturn(ConnectivityManager.TYPE_WIFI)
+ given(activeNetworkInfo.isConnectedOrConnecting).willReturn(true)
+
+ // when
+ val status = classToTest.getNetworkStatus()
+
+ // then
+ assertThat(status, isEqualTo(ConnectionChecker.WIFI))
+ }
+
+ @Test
+ fun offlineWhenActiveNetworkNotConnected() {
+ // given
+ given(connectivityManager.activeNetworkInfo).willReturn(activeNetworkInfo)
+ given(activeNetworkInfo.isConnectedOrConnecting).willReturn(false)
+
+ // when
+ val result = classToTest.isOnline()
+
+ // then
+ assertThat(result, isEqualTo(false))
+ }
+
+
+ @Test
+ fun onlineWhenActiveNetworkConnected() {
+ // given
+ given(connectivityManager.activeNetworkInfo).willReturn(activeNetworkInfo)
+ given(activeNetworkInfo.isConnectedOrConnecting).willReturn(true)
+
+ // when
+ val result = classToTest.isOnline()
+
+ // then
+ assertThat(result, isEqualTo(true))
+ }
+
+ @Test
+ fun networkStatusMobileWhenMobileConnected() {
+ // given
+ given(connectivityManager.activeNetworkInfo).willReturn(activeNetworkInfo)
+ given(activeNetworkInfo.type).willReturn(ConnectivityManager.TYPE_MOBILE)
+ given(activeNetworkInfo.isConnectedOrConnecting).willReturn(true)
+
+ // when
+ val status = classToTest.getNetworkStatus()
+
+ // then
+ assertThat(status, isEqualTo(ConnectionChecker.MOBILE))
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/pro/averin/anton/clean/android/cookbook/ui/common/ConnectionEventsHandlerTest.kt b/app/src/test/java/pro/averin/anton/clean/android/cookbook/ui/common/ConnectionEventsHandlerTest.kt
new file mode 100644
index 0000000..57e375b
--- /dev/null
+++ b/app/src/test/java/pro/averin/anton/clean/android/cookbook/ui/common/ConnectionEventsHandlerTest.kt
@@ -0,0 +1,124 @@
+package pro.averin.anton.clean.android.cookbook.ui.common
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.powermock.core.classloader.annotations.PrepareForTest
+import org.powermock.modules.junit4.PowerMockRunner
+import pro.averin.anton.clean.android.cookbook.data.common.connection.ConnectionChecker
+import pro.averin.anton.clean.android.cookbook.data.common.rx.bus.GlobalBus
+import pro.averin.anton.clean.android.cookbook.data.common.rx.bus.GlobalBusSubscriber
+import pro.averin.anton.clean.android.cookbook.data.common.rx.bus.events.ConnectivityAvailableGlobalEvent
+import pro.averin.anton.clean.android.cookbook.data.common.rx.bus.events.ConnectivityUnavailableGlobalEvent
+import pro.averin.anton.clean.android.cookbook.kotlin.test.*
+import pro.averin.anton.clean.android.cookbook.ui.common.resolution.Resolution
+
+@RunWith(PowerMockRunner::class)
+@PrepareForTest(
+ ConnectionChecker::class
+)
+class ConnectionEventsHandlerTest {
+
+ @Mock private lateinit var connectionChecker: ConnectionChecker
+ @Mock private lateinit var resolution: Resolution
+
+ private val globalBus = GlobalBus()
+ private val globalBusSubscriber = GlobalBusSubscriber(globalBus)
+
+ private lateinit var classToTest: ConnectionEventsHandler
+
+ @Before
+ fun setup() {
+ classToTest = ConnectionEventsHandler(
+ globalBusSubscriber,
+ connectionChecker
+ )
+ }
+
+ @Test
+ fun startSubscribes2Events() {
+ // when
+ classToTest.start(resolution)
+
+ // then
+ assertThat(globalBusSubscriber.subscriptions.size, isEqualTo(2))
+ }
+
+ @Test
+ fun startResolvesConnectivityAvailableOnStart() {
+ // given
+ given(connectionChecker.isOnline()).willReturn(true)
+
+ // when
+ classToTest.start(resolution)
+
+ // then
+ verify(resolution).onConnectivityAvailable()
+ }
+
+ @Test
+ fun startResolvesConnectivityUnavailableOnStart() {
+ // given
+ given(connectionChecker.isOnline()).willReturn(false)
+
+ // when
+ classToTest.start(resolution)
+
+ // then
+ verify(resolution).onConnectivityUnavailable()
+ }
+
+ @Test
+ fun connectivityAvailableEventWhenStoppedDoesNothing() {
+ // when
+ globalBus.post(ConnectivityAvailableGlobalEvent())
+
+ // then
+ verify(resolution, never()).onConnectivityAvailable()
+ }
+
+ @Test
+ fun connectivityUnavailableEventWhenStoppedDoesNothing() {
+ // when
+ globalBus.post(ConnectivityUnavailableGlobalEvent())
+
+ // then
+ verify(resolution, never()).onConnectivityUnavailable()
+ }
+
+ @Test
+ fun connectivityAvailableEventWhenStartedResolved() {
+ // given
+ classToTest.start(resolution)
+
+ // when
+ globalBus.post(ConnectivityAvailableGlobalEvent())
+
+ // then
+ verify(resolution).onConnectivityAvailable()
+ }
+
+ @Test
+ fun connectivityUnavailableEventWhenStartedResolved() {
+ // given
+ classToTest.start(resolution)
+
+ // when
+ reset(resolution)
+ globalBus.post(ConnectivityUnavailableGlobalEvent())
+
+ // then
+ verify(resolution).onConnectivityUnavailable()
+ }
+
+ @Test
+ fun stopUnsubscribesEvents() {
+ // when
+ classToTest.stop()
+
+ // then
+ assertThat(globalBusSubscriber.subscriptions.size, isEqualTo(0))
+ }
+
+}
\ No newline at end of file