diff --git a/app/build.gradle b/app/build.gradle index d54d877d0..907137e68 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -64,18 +64,20 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.preference:preference:1.1.1' implementation "androidx.core:core-ktx:1.3.0" + implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2' implementation 'androidx.navigation:navigation-ui-ktx:2.2.2' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta6' + implementation "androidx.room:room-ktx:2.2.5" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation 'org.videolan.android:libvlc-all:3.1.12' - implementation 'com.google.firebase:firebase-analytics:17.4.2' + implementation 'com.google.firebase:firebase-analytics:17.4.3' implementation 'com.google.firebase:firebase-database:19.3.0' implementation 'com.google.firebase:firebase-database-ktx:19.3.0' implementation 'com.google.android.gms:play-services-auth:18.0.0' - implementation 'com.google.android.material:material:1.1.0' implementation 'io.mavsdk:mavsdk:0.3.1' implementation 'io.mavsdk:mavsdk-server:0.3.1' implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:9.2.0' @@ -87,8 +89,6 @@ dependencies { implementation 'com.mikhaellopez:circularimageview:4.2.0' implementation 'com.github.mastrgamr:mapbox-android-utils:v0.3' implementation 'com.getbase:floatingactionbutton:1.10.1' - implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta6' - implementation "androidx.room:room-ktx:2.2.5" annotationProcessor "androidx.room:room-compiler:2.2.5" testImplementation 'junit:junit:4.13' debugImplementation 'androidx.fragment:fragment-testing:1.2.4' diff --git a/app/src/androidTest/java/ch/epfl/sdp/ui/drone/ReturnDroneDialogFragmentTest.kt b/app/src/androidTest/java/ch/epfl/sdp/ui/drone/ReturnDroneDialogFragmentTest.kt index b9d1ebf13..b29035046 100644 --- a/app/src/androidTest/java/ch/epfl/sdp/ui/drone/ReturnDroneDialogFragmentTest.kt +++ b/app/src/androidTest/java/ch/epfl/sdp/ui/drone/ReturnDroneDialogFragmentTest.kt @@ -128,7 +128,7 @@ class ReturnDroneDialogFragmentTest { ReturnDroneDialogFragment().show(mActivityRule.activity.supportFragmentManager, mActivityRule.activity.getString(R.string.return_drone_dialog)) onView(withText(applicationContext() - .getString(R.string.dialog_cancel))) + .getString(R.string.cancel))) .perform(click()) onView(withText(applicationContext().getString(R.string.return_drone_dialog_title))) diff --git a/app/src/androidTest/java/ch/epfl/sdp/ui/maps/MapActivityTest.kt b/app/src/androidTest/java/ch/epfl/sdp/ui/maps/MapActivityTest.kt index bf707d4a3..6c9397725 100644 --- a/app/src/androidTest/java/ch/epfl/sdp/ui/maps/MapActivityTest.kt +++ b/app/src/androidTest/java/ch/epfl/sdp/ui/maps/MapActivityTest.kt @@ -427,25 +427,6 @@ class MapActivityTest { } } - @Test - fun storeMapButtonIsWorking() { - runOnUiThread { - MainDataManager.goOffline() - MainDataManager.groupId.value = DUMMY_GROUP_ID - MainDataManager.role.value = Role.OPERATOR - } - mActivityRule.launchActivity(Intent()) - - mUiDevice.wait(Until.hasObject(By.desc(applicationContext().getString(R.string.map_ready))), MAP_LOADING_TIMEOUT) - assertThat(mActivityRule.activity.mapView.contentDescription == applicationContext().getString(R.string.map_ready), equalTo(true)) - - onView(withId(R.id.floating_menu_button)).perform(click()) - onView(withId(R.id.store_button)).perform(click()) - onView(withId(R.id.store_button)).perform(click()) - - intended(hasComponent(OfflineManagerActivity::class.java.name)) - } - @Test fun loosingDroneConnectionShowsToast() { runOnUiThread { diff --git a/app/src/androidTest/java/ch/epfl/sdp/ui/maps/offline/OfflineManagerActivityTest.kt b/app/src/androidTest/java/ch/epfl/sdp/ui/maps/offline/OfflineManagerActivityTest.kt index e9dcf368b..dd17595e1 100644 --- a/app/src/androidTest/java/ch/epfl/sdp/ui/maps/offline/OfflineManagerActivityTest.kt +++ b/app/src/androidTest/java/ch/epfl/sdp/ui/maps/offline/OfflineManagerActivityTest.kt @@ -23,7 +23,6 @@ import com.mapbox.mapboxsdk.offline.OfflineManager import com.mapbox.mapboxsdk.offline.OfflineRegion import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.not -import org.hamcrest.Matchers.closeTo import org.junit.Before import org.junit.Rule import org.junit.Test @@ -39,15 +38,12 @@ class OfflineManagerActivityTest { companion object { private const val ZOOM_VALUE = 15.0 - private const val FAKE_MAP_NAME_1 = "RandomName" - private const val FAKE_MAP_NAME_2 = "SEA" + private const val FAKE_MAP_NAME = "Random_Name" private val FAKE_MAP_LOCATION = LatLng(14.0, 12.0) private const val MAP_LOADING_TIMEOUT = 30000L - private const val EPSILON = 1e-0 private const val POSITIVE_BUTTON_ID: Int = android.R.id.button1 private const val NEGATIVE_BUTTON_ID: Int = android.R.id.button2 - private const val NEUTRAL_BUTTON_ID: Int = android.R.id.button3 private const val ASYNC_CALL_TIMEOUT = 5L } @@ -76,51 +72,44 @@ class OfflineManagerActivityTest { } @Test - fun checkToastWhenNoMapsHaveBeenDownloaded() { - onView(withId(R.id.list_button)).perform(click()) - onView(withText(applicationContext().getString(R.string.toast_no_regions_yet))) - .inRoot(withDecorView(not(mActivityRule.activity.window.decorView))) - .check(matches(isDisplayed())) - } - - /** - * this also tests that Toast are shown - */ - @Test - fun canDownloadAndThenDeleteMap() { - //check that the downloaded list map is empty + fun canDownloadMap() { + //Check that the downloaded list map is empty val checkedIfEmpty = CountDownLatch(1) offlineManager.listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback { override fun onList(offlineRegions: Array) { - assertThat(offlineRegions.isEmpty(), equalTo(true)) + val emptiedLatch = CountDownLatch(offlineRegions.size) + offlineRegions.forEach { + it.delete(object : OfflineRegion.OfflineRegionDeleteCallback { + override fun onDelete() { + emptiedLatch.countDown() + } + + override fun onError(error: String?) {} + }) + } + emptiedLatch.await(ASYNC_CALL_TIMEOUT, TimeUnit.SECONDS) + assertThat(emptiedLatch.count, equalTo(0L)) + checkedIfEmpty.countDown() } override fun onError(error: String) {} //left intentionally empty }) - checkedIfEmpty.await(ASYNC_CALL_TIMEOUT, TimeUnit.SECONDS) + checkedIfEmpty.await(ASYNC_CALL_TIMEOUT * 2, TimeUnit.SECONDS) assertThat(checkedIfEmpty.count, equalTo(0L)) - //DOWNLOAD part onView(withId(R.id.download_button)).perform(click()) - onView(withId(R.id.dialog_textfield_id)).perform(typeText(FAKE_MAP_NAME_1)) + onView(withId(R.id.dialog_textfield_id)).perform(typeText(FAKE_MAP_NAME)) mUiDevice.pressBack() onView(withId(POSITIVE_BUTTON_ID)).perform(click()) - mUiDevice.wait(Until.hasObject(By.desc(applicationContext().getString(R.string.map_ready))), MAP_LOADING_TIMEOUT * 30) - assertThat(mActivityRule.activity.mapView.contentDescription == applicationContext().getString(R.string.map_ready), equalTo(true)) - -// onView(withText(applicationContext().getString(R.string.end_progress_success))) -// .inRoot(withDecorView(not(mActivityRule.activity.window.decorView))) -// .check(matches(isDisplayed())) - val calledList = CountDownLatch(1) offlineManager.listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback { override fun onList(offlineRegions: Array) { //check that the region has been downloaded - assertThat(getRegionName(offlineRegions[0]), equalTo(FAKE_MAP_NAME_1)) + assertThat(getRegionName(offlineRegions[0]), equalTo(FAKE_MAP_NAME)) calledList.countDown() } @@ -130,116 +119,19 @@ class OfflineManagerActivityTest { calledList.await(ASYNC_CALL_TIMEOUT, TimeUnit.SECONDS) assertThat(calledList.count, equalTo(0L)) - onView(withId(R.id.list_button)).perform(click()) - onView(withId(NEGATIVE_BUTTON_ID)).perform(click()) - - //DELETE PART - onView(withId(R.id.list_button)).perform(click()) - onView(withId(NEUTRAL_BUTTON_ID)).perform(click()) -// onView(withText(applicationContext().getString(R.string.toast_region_deleted))) -// .inRoot(withDecorView(not(mActivityRule.activity.window.decorView))) -// .check(matches(isDisplayed())) - mUiDevice.wait(Until.hasObject(By.desc(applicationContext().getString(R.string.map_ready))), MAP_LOADING_TIMEOUT) - assertThat(mActivityRule.activity.mapView.contentDescription.toString(), equalTo(applicationContext().getString(R.string.map_ready))) - - //check that the downloaded list map is empty - val calledDelete = CountDownLatch(1) - offlineManager.listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback { - override fun onList(offlineRegions: Array) { - assert(offlineRegions.isEmpty()) - calledDelete.countDown() - } - - override fun onError(error: String) {} //left intentionally empty - }) - calledDelete.await(ASYNC_CALL_TIMEOUT, TimeUnit.SECONDS) - assertThat(calledDelete.count, equalTo(0L)) - } - - /** - * We move the camera over CMA - * Download CMA map - * Then we move the camera somewhere random on the globe - * And finally we try to navigate back to CMA - * - * this also tests that Toast are shown - */ - @Test - fun canNavigateToDownloadedMap() { - val randomLocation = LatLng((-90..90).random().toDouble(), (-180..180).random().toDouble()) - - moveCameraToPosition(FAKE_MAP_LOCATION) - - //check that the downloaded list map is empty - offlineManager.listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback { - override fun onList(offlineRegions: Array) { - assert(offlineRegions.isEmpty()) - } - - override fun onError(error: String) {} //left intentionally empty - }) - - mUiDevice.wait(Until.hasObject(By.desc(applicationContext().getString(R.string.map_ready))), MAP_LOADING_TIMEOUT * 15) - assertThat(mActivityRule.activity.mapView.contentDescription == applicationContext().getString(R.string.map_ready), equalTo(true)) - - //DOWNLOAD Part - onView(withId(R.id.download_button)).perform(click()) - onView(withId(R.id.dialog_textfield_id)).perform(typeText(FAKE_MAP_NAME_2)) - mUiDevice.pressBack() - - onView(withId(POSITIVE_BUTTON_ID)).perform(click()) - - mUiDevice.wait(Until.hasObject(By.desc(applicationContext().getString(R.string.map_ready))), MAP_LOADING_TIMEOUT * 15) - assertThat(mActivityRule.activity.mapView.contentDescription == applicationContext().getString(R.string.map_ready), equalTo(true)) - - onView(withText(applicationContext().getString(R.string.end_progress_success))) - .inRoot(withDecorView(not(mActivityRule.activity.window.decorView))) - .check(matches(isDisplayed())) - - offlineManager.listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback { - override fun onList(offlineRegions: Array) { - //check that the region has been downloaded - assertThat(getRegionName(offlineRegions[0]), equalTo(FAKE_MAP_NAME_2)) - } - - override fun onError(error: String) {} //left intentionally empty - }) - - moveCameraToPosition(randomLocation) - - //NAVIGATE Part - onView(withId(R.id.list_button)).perform(click()) - onView(withId(POSITIVE_BUTTON_ID)).perform(click()) - onView(withText(FAKE_MAP_NAME_2)) - .inRoot(withDecorView(not(mActivityRule.activity.window.decorView))) - .check(matches(isDisplayed())) - - mActivityRule.activity.mapView.getMapAsync { mapboxMap -> - assertThat(mapboxMap.cameraPosition.target.distanceTo(FAKE_MAP_LOCATION), closeTo(0.0, EPSILON)) - } - //DELETE PART - onView(withId(R.id.list_button)).perform(click()) - onView(withId(NEUTRAL_BUTTON_ID)).perform(click()) - mUiDevice.wait(Until.hasObject(By.desc(applicationContext().getString(R.string.map_ready))), MAP_LOADING_TIMEOUT) - assertThat(mActivityRule.activity.mapView.contentDescription == applicationContext().getString(R.string.map_ready), equalTo(true)) - - onView(withText(applicationContext().getString(R.string.toast_region_deleted))) - .inRoot(withDecorView(not(mActivityRule.activity.window.decorView))) - .check(matches(isDisplayed())) - - //check that the downloaded list map is empty - val called = CountDownLatch(1) offlineManager.listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback { override fun onList(offlineRegions: Array) { - assertThat(offlineRegions.isEmpty(), equalTo(true)) - called.countDown() + offlineRegions.forEach { it -> + it.delete(object : OfflineRegion.OfflineRegionDeleteCallback { + override fun onDelete() {} + override fun onError(error: String?) {} + }) + } } - override fun onError(error: String) {} //left intentionally empty + override fun onError(error: String?) {} }) - called.await(ASYNC_CALL_TIMEOUT, TimeUnit.SECONDS) - assertThat(called.count, equalTo(0L)) } @Test diff --git a/app/src/androidTest/java/ch/epfl/sdp/ui/maps/offline/OfflineMapsManagingTest.kt b/app/src/androidTest/java/ch/epfl/sdp/ui/maps/offline/OfflineMapsManagingTest.kt new file mode 100644 index 000000000..497bc7fd6 --- /dev/null +++ b/app/src/androidTest/java/ch/epfl/sdp/ui/maps/offline/OfflineMapsManagingTest.kt @@ -0,0 +1,164 @@ +package ch.epfl.sdp.ui.maps.offline + +import android.Manifest.permission.ACCESS_FINE_LOCATION +import android.view.Gravity +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.typeText +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.contrib.DrawerActions +import androidx.test.espresso.contrib.DrawerMatchers.isClosed +import androidx.test.espresso.contrib.NavigationViewActions +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.matcher.IntentMatchers +import androidx.test.espresso.intent.rule.IntentsTestRule +import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import androidx.test.rule.GrantPermissionRule +import androidx.test.rule.GrantPermissionRule.grant +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import ch.epfl.sdp.MainApplication +import ch.epfl.sdp.R +import ch.epfl.sdp.map.MapUtils +import ch.epfl.sdp.ui.MainActivity +import com.mapbox.mapboxsdk.geometry.LatLng +import com.mapbox.mapboxsdk.offline.OfflineManager +import com.mapbox.mapboxsdk.offline.OfflineRegion +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class OfflineMapsManagingTest { + + private lateinit var mUiDevice: UiDevice + private lateinit var offlineManager: OfflineManager + + companion object { + private const val FAKE_MAP_NAME_1 = "RandomName_1" + private const val FAKE_MAP_NAME_2 = "RandomName_2" + private const val POSITIVE_BUTTON_ID = android.R.id.button1 + private const val MAP_DOWNLOADING_TIMEOUT = 4000L + private const val MAP_LOADING_TIMEOUT = 1000L + } + + @Rule + @JvmField + val grantPermissionRule: GrantPermissionRule = grant(ACCESS_FINE_LOCATION, ACCESS_FINE_LOCATION) + + @get:Rule + val mActivityRule = IntentsTestRule(MainActivity::class.java) + + @Before + @Throws(Exception::class) + fun before() { + mUiDevice = UiDevice.getInstance(getInstrumentation()) + offlineManager = OfflineManager.getInstance(MainApplication.applicationContext()) + } + + private fun openDrawer() { + onView(withId(R.id.drawer_layout)) + .check(matches(isClosed(Gravity.LEFT))) // Check that drawer is closed to begin with + .perform(DrawerActions.open()) + } + + @Test + fun canNavigateToMapsManaging() { + openDrawer() + onView(withId(R.id.nav_view)) + .perform(NavigationViewActions.navigateTo(R.id.nav_maps_managing)) + } + + @Test + fun canLaunchOfflineManagerActivity() { + openDrawer() + onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_maps_managing)) + mUiDevice.wait(Until.hasObject(By.desc(MainApplication.applicationContext().getString(R.string.no_offline_map_downloaded_yet))), MAP_LOADING_TIMEOUT) + + onView(withId(R.id.store_button)).perform(click()) + + Intents.intended(IntentMatchers.hasComponent(OfflineManagerActivity::class.java.name)) + } + + @Test + fun canNavigateToDownloadedMap() { + MapUtils.saveCameraPositionAndZoomToPrefs( + MapUtils.getCameraWithParameters( + LatLng(0.0, 0.0), + 20.0 + ) + ) + + openDrawer() + onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_maps_managing)) + mUiDevice.wait(Until.hasObject(By.desc(MainApplication.applicationContext().getString(R.string.no_offline_map_downloaded_yet))), MAP_LOADING_TIMEOUT) + + onView(withId(R.id.store_button)).perform(click()) + mUiDevice.wait(Until.hasObject(By.desc(MainApplication.applicationContext().getString(R.string.dialog_positive_button))), MAP_LOADING_TIMEOUT) + + //DOWNLOAD Part + onView(withId(R.id.download_button)).perform(click()) + onView(withId(R.id.dialog_textfield_id)).perform(typeText(FAKE_MAP_NAME_1)) + mUiDevice.pressBack() + + onView(withId(POSITIVE_BUTTON_ID)).perform(click()) + mUiDevice.wait(Until.hasObject(By.desc(FAKE_MAP_NAME_1)), MAP_DOWNLOADING_TIMEOUT) + + onView((withText(FAKE_MAP_NAME_1))).perform(click()) + onView(withText(R.string.delete)).check(matches(isDisplayed())) + + //DELETE PART + offlineManager.listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback { + override fun onList(offlineRegions: Array) { + offlineRegions.forEach { + it.delete(object : OfflineRegion.OfflineRegionDeleteCallback { + override fun onDelete() {} + override fun onError(error: String?) {} + }) + } + } + + override fun onError(error: String?) {} + }) + } + + @Test + fun canDeleteDownloadedMap() { + MapUtils.saveCameraPositionAndZoomToPrefs( + MapUtils.getCameraWithParameters( + LatLng(1.0, 1.0), + 20.0 + ) + ) + + openDrawer() + onView(withId(R.id.nav_view)) + .perform(NavigationViewActions.navigateTo(R.id.nav_maps_managing)) + mUiDevice.wait(Until.hasObject(By.desc(MainApplication.applicationContext().getString(R.string.no_offline_map_downloaded_yet))), MAP_LOADING_TIMEOUT) + + onView(withId(R.id.store_button)) + .perform(click()) + mUiDevice.wait(Until.hasObject(By.desc(MainApplication.applicationContext().getString(R.string.dialog_positive_button))), MAP_LOADING_TIMEOUT) + + //DOWNLOAD Part + onView(withId(R.id.download_button)).perform(click()) + onView(withId(R.id.dialog_textfield_id)).perform(typeText(FAKE_MAP_NAME_2)) + mUiDevice.pressBack() + + onView(withId(POSITIVE_BUTTON_ID)).perform(click()) + mUiDevice.wait(Until.hasObject(By.desc(FAKE_MAP_NAME_2)), MAP_DOWNLOADING_TIMEOUT) + + onView((withText(FAKE_MAP_NAME_2))).perform(click()) + + mUiDevice.wait(Until.hasObject(By.desc(MainApplication.applicationContext().getString(R.string.delete))), MAP_DOWNLOADING_TIMEOUT) + onView(withId(R.id.delete_offline_map_button)).perform(click()) + onView(withText(R.string.delete)).perform(click()) + + mUiDevice.wait(Until.hasObject(By.desc(MainApplication.applicationContext().getString(R.string.no_offline_map_downloaded_yet))), MAP_DOWNLOADING_TIMEOUT) + onView(withText(R.string.no_offline_map_downloaded_yet)).check(matches(isDisplayed())) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 86b4c3142..bb93ed6e8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,7 +15,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:screenOrientation="landscape" android:supportsRtl="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme.NoActionBar"> + android:screenOrientation="landscape" /> + android:parentActivityName=".ui.MainActivity" /> - \ No newline at end of file diff --git a/app/src/main/java/ch/epfl/sdp/map/offline/OfflineRegionUtils.kt b/app/src/main/java/ch/epfl/sdp/map/offline/OfflineRegionUtils.kt index 00bff5341..a92d911dc 100644 --- a/app/src/main/java/ch/epfl/sdp/map/offline/OfflineRegionUtils.kt +++ b/app/src/main/java/ch/epfl/sdp/map/offline/OfflineRegionUtils.kt @@ -6,7 +6,9 @@ import ch.epfl.sdp.MainApplication import ch.epfl.sdp.R import ch.epfl.sdp.map.offline.DownloadProgressBarUtils.hideProgressBar import ch.epfl.sdp.ui.maps.offline.OfflineManagerActivity +import com.mapbox.mapboxsdk.camera.CameraPosition import com.mapbox.mapboxsdk.maps.MapView +import com.mapbox.mapboxsdk.offline.OfflineManager import com.mapbox.mapboxsdk.offline.OfflineRegion import org.json.JSONObject import timber.log.Timber @@ -38,6 +40,28 @@ object OfflineRegionUtils { .getString(OfflineManagerActivity.JSON_FIELD_REGION_NAME) } + fun getRegionLocation(offlineRegion: OfflineRegion): CameraPosition { + val lat = JSONObject(String(offlineRegion.metadata, Charset.forName(OfflineManagerActivity.JSON_CHARSET))) + .getString(OfflineManagerActivity.JSON_FIELD_REGION_LOCATION_LATITUDE).toDouble() + val lng = JSONObject(String(offlineRegion.metadata, Charset.forName(OfflineManagerActivity.JSON_CHARSET))) + .getString(OfflineManagerActivity.JSON_FIELD_REGION_LOCATION_LONGITUDE).toDouble() + val zoom = JSONObject(String(offlineRegion.metadata, Charset.forName(OfflineManagerActivity.JSON_CHARSET))) + .getString(OfflineManagerActivity.JSON_FIELD_REGION_ZOOM).toDouble() + return CameraPosition.Builder().target(com.mapbox.mapboxsdk.geometry.LatLng(lat, lng)).zoom(zoom).build() + } + + fun getRegionById(id: Long, callback: (OfflineRegion?) -> Unit) { + OfflineManager.getInstance(MainApplication.applicationContext()).listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback { + override fun onList(offlineRegions: Array?) { + callback(offlineRegions?.filter { it.id == id }?.first()) + } + + override fun onError(error: String?) { + callback(null) + } + }) + } + fun showErrorAndToast(message: String) { Timber.e(message) Toast.makeText(MainApplication.applicationContext(), message, Toast.LENGTH_LONG).show() diff --git a/app/src/main/java/ch/epfl/sdp/ui/MainActivity.kt b/app/src/main/java/ch/epfl/sdp/ui/MainActivity.kt index 1382c69dc..33c6d15c3 100644 --- a/app/src/main/java/ch/epfl/sdp/ui/MainActivity.kt +++ b/app/src/main/java/ch/epfl/sdp/ui/MainActivity.kt @@ -1,7 +1,6 @@ package ch.epfl.sdp.ui import android.content.Intent -import android.graphics.Color import android.os.Build import android.os.Bundle import android.view.Menu @@ -17,21 +16,17 @@ import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.navigateUp import androidx.navigation.ui.setupWithNavController import ch.epfl.sdp.R -import ch.epfl.sdp.database.data.Role import ch.epfl.sdp.database.data_manager.MainDataManager -import ch.epfl.sdp.drone.Drone import ch.epfl.sdp.ui.maps.MapActivity +import ch.epfl.sdp.ui.maps.offline.OfflineManagerActivity import ch.epfl.sdp.ui.search_group.selection.SearchGroupSelectionActivity import ch.epfl.sdp.ui.settings.SettingsActivity import ch.epfl.sdp.utils.Auth -import ch.epfl.sdp.utils.CentralLocationManager import com.google.android.material.navigation.NavigationView -import com.google.android.material.snackbar.Snackbar class MainActivity : AppCompatActivity() { private lateinit var appBarConfiguration: AppBarConfiguration - private lateinit var snackbar: Snackbar @RequiresApi(Build.VERSION_CODES.N) override fun onCreate(savedInstanceState: Bundle?) { @@ -45,8 +40,6 @@ class MainActivity : AppCompatActivity() { private fun configureNavigationView() { val drawerLayout = findViewById(R.id.drawer_layout) val navView = findViewById(R.id.nav_view) - snackbar = Snackbar.make(navView, R.string.not_connected_message, Snackbar.LENGTH_LONG) - .setBackgroundTint(Color.BLACK).setTextColor(Color.WHITE) val navController = findNavController(R.id.nav_host_fragment) navView.setupWithNavController(navController) @@ -66,15 +59,6 @@ class MainActivity : AppCompatActivity() { override fun onStart() { super.onStart() MainDataManager.goOnline() - CentralLocationManager.configure(this) - if (MainDataManager.role.value == Role.OPERATOR && !Drone.isConnectedLiveData.value!!) { - snackbar.show() - } - } - - override fun onDestroy() { - super.onDestroy() - CentralLocationManager.onDestroy() } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -93,12 +77,6 @@ class MainActivity : AppCompatActivity() { startActivity(Intent(this, SettingsActivity::class.java)) } - override fun onRequestPermissionsResult(requestCode: Int, - permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - CentralLocationManager.onRequestPermissionsResult(requestCode, permissions, grantResults) - } - private fun checkConnexion(view: View, action: () -> Unit) { if (Auth.loggedIn.value == false) { Auth.login(this) { success -> @@ -144,4 +122,8 @@ class MainActivity : AppCompatActivity() { val drawerLayout = findViewById(R.id.drawer_layout) drawerLayout.openDrawer(GravityCompat.START) } + + fun openMapForOfflineDownload(view: View?) { + startActivity(Intent(this, OfflineManagerActivity::class.java)) + } } diff --git a/app/src/main/java/ch/epfl/sdp/ui/dialog/DeleteConfirmDialogFragment.kt b/app/src/main/java/ch/epfl/sdp/ui/dialog/DeleteConfirmDialogFragment.kt new file mode 100644 index 000000000..f8b8f011d --- /dev/null +++ b/app/src/main/java/ch/epfl/sdp/ui/dialog/DeleteConfirmDialogFragment.kt @@ -0,0 +1,23 @@ +package ch.epfl.sdp.ui.dialog + +import android.app.AlertDialog +import android.app.Dialog +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import ch.epfl.sdp.R + +class DeleteConfirmDialogFragment(private val title: String, private val onDelete: () -> Unit) : DialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return activity.let { + AlertDialog.Builder(it) + .setTitle(title) + .setPositiveButton(getString(R.string.navigate_neutral_button_title)) { _, _ -> + onDelete() + } + // When the user cancels, don't do anything. + // The dialog will automatically close + .setNegativeButton(getString(R.string.cancel)) { _, _ -> }.create() + } ?: throw IllegalStateException("Activity cannot be null") + } +} + diff --git a/app/src/main/java/ch/epfl/sdp/ui/drone/ReturnDroneDialogFragment.kt b/app/src/main/java/ch/epfl/sdp/ui/drone/ReturnDroneDialogFragment.kt index ad9cd3a38..74a720533 100644 --- a/app/src/main/java/ch/epfl/sdp/ui/drone/ReturnDroneDialogFragment.kt +++ b/app/src/main/java/ch/epfl/sdp/ui/drone/ReturnDroneDialogFragment.kt @@ -28,7 +28,7 @@ class ReturnDroneDialogFragment : DialogFragment() { tryReturnHome(it) } } - .setNegativeButton(getString(R.string.dialog_cancel)) { dialog, _ -> dialog.cancel() } + .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> dialog.cancel() } // Display the dialog // Create the AlertDialog object and return it builder.create() diff --git a/app/src/main/java/ch/epfl/sdp/ui/maps/MapActivity.kt b/app/src/main/java/ch/epfl/sdp/ui/maps/MapActivity.kt index f51fa5708..ed0c07dd6 100644 --- a/app/src/main/java/ch/epfl/sdp/ui/maps/MapActivity.kt +++ b/app/src/main/java/ch/epfl/sdp/ui/maps/MapActivity.kt @@ -1,7 +1,6 @@ package ch.epfl.sdp.ui.maps import android.Manifest -import android.content.Intent import android.content.pm.PackageManager import android.os.Bundle import android.view.View @@ -31,7 +30,6 @@ import ch.epfl.sdp.searcharea.CircleBuilder import ch.epfl.sdp.searcharea.QuadrilateralBuilder import ch.epfl.sdp.searcharea.SearchAreaBuilder import ch.epfl.sdp.ui.drone.ReturnDroneDialogFragment -import ch.epfl.sdp.ui.maps.offline.OfflineManagerActivity import ch.epfl.sdp.utils.Auth import ch.epfl.sdp.utils.CentralLocationManager import ch.epfl.sdp.utils.StrategyUtils.loadDefaultStrategyFromPreferences @@ -232,6 +230,12 @@ class MapActivity : MapViewBaseActivity(), OnMapReadyCallback, MapboxMap.OnMapLo locationComponent.renderMode = RenderMode.COMPASS } + if (MainDataManager.role.value == Role.OPERATOR) { + val logoMargin = resources.getDimension(R.dimen.tiny_margin).toInt() + val logoOffset = resources.getDimension(R.dimen.map_activity_small_camera_width).toInt() + logoMargin * 2 + mapboxMap.uiSettings.setAttributionMargins(logoOffset + mapboxMap.uiSettings.attributionMarginLeft, 0, 0, logoMargin) + mapboxMap.uiSettings.setLogoMargins(logoOffset, 0, 0, logoMargin) + } isMapReady = true @@ -321,13 +325,6 @@ class MapActivity : MapViewBaseActivity(), OnMapReadyCallback, MapboxMap.OnMapLo searchAreaBuilder.reset() } - /** - * Starts OfflineManagerActivity - */ - fun storeMap(v: View) { - startActivity(Intent(applicationContext, OfflineManagerActivity::class.java)) - } - /** * Clears the waypoints list and removes all the lines and points related to waypoints */ diff --git a/app/src/main/java/ch/epfl/sdp/ui/maps/offline/DownloadRegionDialogFragment.kt b/app/src/main/java/ch/epfl/sdp/ui/maps/offline/DownloadRegionDialogFragment.kt index bad68799b..7f717355e 100644 --- a/app/src/main/java/ch/epfl/sdp/ui/maps/offline/DownloadRegionDialogFragment.kt +++ b/app/src/main/java/ch/epfl/sdp/ui/maps/offline/DownloadRegionDialogFragment.kt @@ -37,7 +37,7 @@ class DownloadRegionDialogFragment : DialogFragment() { (activity as OfflineManagerActivity).prepareAndLaunchDownload(regionName) } } - .setNegativeButton(getString(R.string.dialog_cancel)) { dialog, _ -> dialog.cancel() } + .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> dialog.cancel() } // Display the dialog builder.create() } ?: throw IllegalStateException("Activity cannot be null") diff --git a/app/src/main/java/ch/epfl/sdp/ui/maps/offline/ListOfflineRegionDialogFragment.kt b/app/src/main/java/ch/epfl/sdp/ui/maps/offline/ListOfflineRegionDialogFragment.kt deleted file mode 100644 index 19baddf66..000000000 --- a/app/src/main/java/ch/epfl/sdp/ui/maps/offline/ListOfflineRegionDialogFragment.kt +++ /dev/null @@ -1,50 +0,0 @@ -package ch.epfl.sdp.ui.maps.offline - -import android.app.AlertDialog -import android.app.Dialog -import android.os.Bundle -import android.widget.ProgressBar -import android.widget.Toast -import androidx.fragment.app.DialogFragment -import ch.epfl.sdp.R -import ch.epfl.sdp.map.MapUtils -import ch.epfl.sdp.map.offline.DownloadProgressBarUtils -import ch.epfl.sdp.map.offline.OfflineRegionUtils -import com.mapbox.mapboxsdk.maps.MapView -import com.mapbox.mapboxsdk.maps.MapboxMap -import com.mapbox.mapboxsdk.offline.OfflineRegion - -class ListOfflineRegionDialogFragment(private val items: Array, - private val offlineRegions: Array, - private val mapboxMap: MapboxMap, - private val progressBar: ProgressBar, - private val mapView: MapView) : DialogFragment() { - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - return activity.let { - var regionSelected = 0 - AlertDialog.Builder(it) - .setTitle(getString(R.string.navigate_title)).setSingleChoiceItems(items, 0) { _, which -> regionSelected = which } - .setPositiveButton(getString(R.string.navigate_positive_button)) { _, _ -> - Toast.makeText(it, items[regionSelected], Toast.LENGTH_LONG).show() - // Create new camera position - val definition = offlineRegions[regionSelected].definition - mapboxMap.cameraPosition = MapUtils.getCameraWithParameters( - definition.bounds.center, - definition.minZoom) - } - .setNeutralButton(getString(R.string.navigate_neutral_button_title)) { _, _ -> - // Make progressBar indeterminate and - // set it to visible to signal that - // the deletion process has begun - DownloadProgressBarUtils.deletingInProgress(progressBar) - // Begin the deletion process - OfflineRegionUtils.deleteOfflineRegion(offlineRegions[regionSelected], progressBar, mapView) - } - // When the user cancels, don't do anything. - // The dialog will automatically close - .setNegativeButton(getString(R.string.dialog_cancel) - ) { _, _ -> }.create() - } ?: throw IllegalStateException("Activity cannot be null") - } -} - diff --git a/app/src/main/java/ch/epfl/sdp/ui/maps/offline/MapSelectionRecyclerViewAdapter.kt b/app/src/main/java/ch/epfl/sdp/ui/maps/offline/MapSelectionRecyclerViewAdapter.kt new file mode 100644 index 000000000..c1cba129f --- /dev/null +++ b/app/src/main/java/ch/epfl/sdp/ui/maps/offline/MapSelectionRecyclerViewAdapter.kt @@ -0,0 +1,23 @@ +package ch.epfl.sdp.ui.maps.offline + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import ch.epfl.sdp.utils.OnItemClickListener +import com.mapbox.mapboxsdk.offline.OfflineRegion + +class MapSelectionRecyclerViewAdapter( + private val regions: Array, + private val itemClickListener: OnItemClickListener) + : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OfflineMapViewHolder { + return OfflineMapViewHolder(LayoutInflater.from(parent.context), parent) + } + + override fun onBindViewHolder(holder: OfflineMapViewHolder, position: Int) { + holder.bind(regions[position], itemClickListener) + } + + override fun getItemCount(): Int = regions.size +} \ No newline at end of file diff --git a/app/src/main/java/ch/epfl/sdp/ui/maps/offline/MapsManagingFragment.kt b/app/src/main/java/ch/epfl/sdp/ui/maps/offline/MapsManagingFragment.kt index ceb53ad6e..6bb95348a 100644 --- a/app/src/main/java/ch/epfl/sdp/ui/maps/offline/MapsManagingFragment.kt +++ b/app/src/main/java/ch/epfl/sdp/ui/maps/offline/MapsManagingFragment.kt @@ -1,30 +1,71 @@ package ch.epfl.sdp.ui.maps.offline +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import ch.epfl.sdp.MainApplication import ch.epfl.sdp.R +import ch.epfl.sdp.map.offline.OfflineRegionUtils +import ch.epfl.sdp.utils.OnItemClickListener +import com.mapbox.mapboxsdk.offline.OfflineManager +import com.mapbox.mapboxsdk.offline.OfflineRegion +import kotlinx.android.synthetic.main.fragment_maps_managing.* class MapsManagingFragment : Fragment() { private lateinit var mapsManagingViewModel: MapsManagingViewModel - + private lateinit var offlineManager: OfflineManager override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { mapsManagingViewModel = ViewModelProvider(this).get(MapsManagingViewModel::class.java) - val root = inflater.inflate(R.layout.fragment_maps_managing, container, false) - val textView: TextView = root.findViewById(R.id.text_slideshow) - mapsManagingViewModel.text.observe(viewLifecycleOwner, Observer { - textView.text = getString(R.string.maps_managing) + offlineManager = OfflineManager.getInstance(MainApplication.applicationContext()) + return inflater.inflate(R.layout.fragment_maps_managing, container, false) + } + + override fun onStart() { + super.onStart() + offlineManager.listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback { + override fun onList(offlineRegions: Array) { // Check result. If no regions have been + // RecyclerView node initialized here + mapSelectionRecyclerview.apply { + // set a LinearLayoutManager to handle Android + // RecyclerView behavior + layoutManager = LinearLayoutManager(activity) + // set the custom adapter to the RecyclerView + adapter = MapSelectionRecyclerViewAdapter(offlineRegions, object : OnItemClickListener { + override fun onItemClicked(offlineRegion: OfflineRegion) { + openExistingRegion(offlineRegion) + } + + }) + } + view?.findViewById(R.id.no_offline_map)?.visibility = if (offlineRegions.isNotEmpty()) { + View.GONE + } else { + View.VISIBLE + } + } + + override fun onError(error: String) { + OfflineRegionUtils.showErrorAndToast("Error : $error") + } }) - return root + } + + private fun openExistingRegion(offlineRegion: OfflineRegion) { + val context = MainApplication.applicationContext() + val intent = Intent(context, OfflineManagerActivity::class.java) + intent.putExtra(getString(R.string.intent_key_show_delete_button), true) + intent.putExtra(getString(R.string.intent_key_offline_region_id), offlineRegion.id) + startActivity(intent) } } diff --git a/app/src/main/java/ch/epfl/sdp/ui/maps/offline/OfflineManagerActivity.kt b/app/src/main/java/ch/epfl/sdp/ui/maps/offline/OfflineManagerActivity.kt index 9a555290c..dc2a4d543 100644 --- a/app/src/main/java/ch/epfl/sdp/ui/maps/offline/OfflineManagerActivity.kt +++ b/app/src/main/java/ch/epfl/sdp/ui/maps/offline/OfflineManagerActivity.kt @@ -4,14 +4,17 @@ import android.os.Bundle import android.view.View import android.widget.Button import android.widget.ProgressBar +import android.widget.TextView import android.widget.Toast import ch.epfl.sdp.R import ch.epfl.sdp.map.MapUtils +import ch.epfl.sdp.map.offline.DownloadProgressBarUtils import ch.epfl.sdp.map.offline.DownloadProgressBarUtils.downloadingInProgress import ch.epfl.sdp.map.offline.DownloadProgressBarUtils.endProgress import ch.epfl.sdp.map.offline.DownloadProgressBarUtils.startProgress -import ch.epfl.sdp.map.offline.OfflineRegionUtils.getRegionName +import ch.epfl.sdp.map.offline.OfflineRegionUtils import ch.epfl.sdp.map.offline.OfflineRegionUtils.showErrorAndToast +import ch.epfl.sdp.ui.dialog.DeleteConfirmDialogFragment import ch.epfl.sdp.ui.maps.MapViewBaseActivity import com.mapbox.mapboxsdk.maps.MapboxMap import com.mapbox.mapboxsdk.maps.OnMapReadyCallback @@ -24,6 +27,7 @@ import org.json.JSONObject import timber.log.Timber import kotlin.math.roundToInt + /** * Download, view, navigate to, and delete an offline region. * @@ -33,30 +37,52 @@ import kotlin.math.roundToInt class OfflineManagerActivity : MapViewBaseActivity(), OnMapReadyCallback { private lateinit var mapboxMap: MapboxMap private lateinit var downloadButton: Button - private lateinit var listButton: Button + private lateinit var cancelButton: Button private lateinit var offlineManager: OfflineManager private lateinit var progressBar: ProgressBar - - private var regionSelected = 0 + private var currentOfflineRegion: OfflineRegion? = null companion object { // JSON encoding/decoding const val JSON_CHARSET = "UTF-8" const val JSON_FIELD_REGION_NAME = "FIELD_REGION_NAME" + const val JSON_FIELD_REGION_LOCATION_LATITUDE = "FIELD_REGION_LOCATION_LATITUDE" + const val JSON_FIELD_REGION_LOCATION_LONGITUDE = "FIELD_REGION_LOCATION_LONGITUDE" + const val JSON_FIELD_REGION_ZOOM = "FIELD_REGION_ZOOM" const val MAX_ZOOM = 20.0 // val maxZoom = map!!.maxZoomLevel //max Zoom is 25.5 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) super.initMapView(savedInstanceState, R.layout.activity_offline_manager, R.id.mapView) - mapView.getMapAsync(this) + + //TODO keep only one intent + val showDelete = intent.getBooleanExtra(getString(R.string.intent_key_show_delete_button), false) + val id = intent.getLongExtra(getString(R.string.intent_key_offline_region_id), -1) + if (!showDelete) { + findViewById