diff --git a/.gitmodules b/.gitmodules
index 07770fff..5daf7267 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -2,3 +2,7 @@
path = gn_mobile_core
url = https://github.com/PnX-SI/gn_mobile_core.git
branch = develop
+[submodule "gn_mobile_maps"]
+ path = gn_mobile_maps
+ url = https://github.com/PnX-SI/gn_mobile_maps.git
+ branch = develop
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 5801bf00..b55e8d05 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,5 +1,12 @@
+
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 82c163ca..811a04c9 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -10,6 +10,7 @@
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index fdb32216..7b7b2fe7 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -3,5 +3,6 @@
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 012d0b4e..08bc00a8 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,19 @@
## Upgrade git sub modules
-Do **NOT** modify directly `commons` and `viewpager`. Any changes should be made from
-[gn_mobile_core](https://github.com/PnX-SI/gn_mobile_core) git repository.
+Do **NOT** modify directly any git sub modules (e.g. `commons`, `viewpager` and `maps`).
+Any changes should be made from each underlying git repository:
+
+* `commons`: [gn_mobile_core](https://github.com/PnX-SI/gn_mobile_core) git repository
+* `viewpager`: [gn_mobile_core](https://github.com/PnX-SI/gn_mobile_core) git repository
+* `maps`: [gn_mobile_maps](https://github.com/PnX-SI/gn_mobile_maps) git repository
```bash
./upgrade_submodules.sh
-```
\ No newline at end of file
+```
+
+## Troubleshooting
+
+* Kotlin error, Redeclaration from class within imported module:
+
+ clean project from menu *Build -> Clean Project*, then rebuild project.
diff --git a/build.gradle b/build.gradle
index 93172c6d..fc0b5243 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.3.31'
+ ext.kotlin_version = '1.3.41'
repositories {
google()
@@ -10,7 +10,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.4.1'
+ classpath 'com.android.tools.build:gradle:3.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/gn_mobile_core b/gn_mobile_core
index bda13bfa..983793e6 160000
--- a/gn_mobile_core
+++ b/gn_mobile_core
@@ -1 +1 @@
-Subproject commit bda13bfa539cbe61a8bc357e34cf31a2436415eb
+Subproject commit 983793e6317734a05c6c390ab5eab555e27672f9
diff --git a/gn_mobile_maps b/gn_mobile_maps
new file mode 160000
index 00000000..30654751
--- /dev/null
+++ b/gn_mobile_maps
@@ -0,0 +1 @@
+Subproject commit 306547510f982ad61631843cf81a3298c35e0583
diff --git a/gn_mobile_occtax.iml b/gn_mobile_occtax.iml
index 7ece0648..8facbe76 100644
--- a/gn_mobile_occtax.iml
+++ b/gn_mobile_occtax.iml
@@ -8,7 +8,7 @@
-
+
diff --git a/maps b/maps
new file mode 120000
index 00000000..15f2186d
--- /dev/null
+++ b/maps
@@ -0,0 +1 @@
+gn_mobile_maps/maps
\ No newline at end of file
diff --git a/occtax/build.gradle b/occtax/build.gradle
index be2f8364..c6b563f9 100644
--- a/occtax/build.gradle
+++ b/occtax/build.gradle
@@ -2,11 +2,16 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
-version = "0.0.7"
+version = "0.1.2"
android {
compileSdkVersion 28
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
defaultConfig {
applicationId "fr.geonature.occtax"
minSdkVersion 21
@@ -43,15 +48,16 @@ dependencies {
implementation project(':commons')
implementation project(':viewpager')
+ implementation project(':maps')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1"
- implementation 'androidx.core:core-ktx:1.2.0-alpha01'
+ implementation 'androidx.core:core-ktx:1.2.0-alpha02'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.0.0'
- implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha06'
+ implementation 'androidx.recyclerview:recyclerview:1.1.0-beta01'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.preference:preference:1.0.0'
implementation 'com.l4digital.fastscroll:fastscroll:2.0.1'
diff --git a/occtax/src/main/java/fr/geonature/occtax/input/Input.kt b/occtax/src/main/java/fr/geonature/occtax/input/Input.kt
index 000478b2..baee058b 100644
--- a/occtax/src/main/java/fr/geonature/occtax/input/Input.kt
+++ b/occtax/src/main/java/fr/geonature/occtax/input/Input.kt
@@ -4,6 +4,7 @@ import android.os.Parcel
import android.os.Parcelable
import fr.geonature.commons.input.AbstractInput
import fr.geonature.commons.input.AbstractInputTaxon
+import org.locationtech.jts.geom.Geometry
/**
* Describes a current input.
@@ -12,8 +13,20 @@ import fr.geonature.commons.input.AbstractInputTaxon
*/
class Input : AbstractInput {
+ var geometry: Geometry? = null
+
constructor() : super("occtax")
- constructor(source: Parcel) : super(source)
+ constructor(source: Parcel) : super(source) {
+ this.geometry = source.readSerializable() as Geometry?
+ }
+
+ override fun writeToParcel(dest: Parcel,
+ flags: Int) {
+ super.writeToParcel(dest,
+ flags)
+
+ dest.writeSerializable(geometry)
+ }
override fun getTaxaFromParcel(source: Parcel): List {
val inputTaxa = source.createTypedArrayList(InputTaxon.CREATOR)
diff --git a/occtax/src/main/java/fr/geonature/occtax/input/io/OnInputJsonReaderListenerImpl.kt b/occtax/src/main/java/fr/geonature/occtax/input/io/OnInputJsonReaderListenerImpl.kt
index 8f42431b..f7b967da 100644
--- a/occtax/src/main/java/fr/geonature/occtax/input/io/OnInputJsonReaderListenerImpl.kt
+++ b/occtax/src/main/java/fr/geonature/occtax/input/io/OnInputJsonReaderListenerImpl.kt
@@ -5,6 +5,7 @@ import android.util.JsonToken
import fr.geonature.commons.input.AbstractInput
import fr.geonature.commons.input.io.InputJsonReader
import fr.geonature.commons.util.IsoDateUtils
+import fr.geonature.maps.jts.geojson.io.GeoJsonReader
import fr.geonature.occtax.input.Input
import fr.geonature.occtax.input.InputTaxon
import java.util.Date
@@ -14,15 +15,17 @@ import java.util.Date
*
* @author [S. Grimault](mailto:sebastien.grimault@gmail.com)
*/
-class OnInputJsonReaderListenerImpl : InputJsonReader.OnInputJsonReaderListener {
+class OnInputJsonReaderListenerImpl : InputJsonReader.OnInputJsonReaderListener {
- override fun createInput(): AbstractInput {
+ private val geoJsonReader = GeoJsonReader()
+
+ override fun createInput(): Input {
return Input()
}
override fun readAdditionalInputData(reader: JsonReader,
keyName: String,
- input: AbstractInput) {
+ input: Input) {
when (keyName) {
"geometry" -> readGeometry(reader,
input)
@@ -33,12 +36,14 @@ class OnInputJsonReaderListenerImpl : InputJsonReader.OnInputJsonReaderListener
}
private fun readGeometry(reader: JsonReader,
- input: AbstractInput) {
- reader.beginObject()
-
- // TODO: read geometry object
-
- reader.endObject()
+ input: Input) {
+ when (reader.peek()) {
+ JsonToken.NULL -> reader.nextNull()
+ JsonToken.BEGIN_OBJECT -> {
+ input.geometry = geoJsonReader.readGeometry(reader)
+ }
+ else -> reader.skipValue()
+ }
}
private fun readProperties(reader: JsonReader,
diff --git a/occtax/src/main/java/fr/geonature/occtax/input/io/OnInputJsonWriterListenerImpl.kt b/occtax/src/main/java/fr/geonature/occtax/input/io/OnInputJsonWriterListenerImpl.kt
index f8d2c483..6d4cf91b 100644
--- a/occtax/src/main/java/fr/geonature/occtax/input/io/OnInputJsonWriterListenerImpl.kt
+++ b/occtax/src/main/java/fr/geonature/occtax/input/io/OnInputJsonWriterListenerImpl.kt
@@ -1,20 +1,23 @@
package fr.geonature.occtax.input.io
import android.util.JsonWriter
-import fr.geonature.commons.input.AbstractInput
import fr.geonature.commons.input.AbstractInputTaxon
import fr.geonature.commons.input.io.InputJsonWriter
import fr.geonature.commons.util.IsoDateUtils
+import fr.geonature.maps.jts.geojson.io.GeoJsonWriter
+import fr.geonature.occtax.input.Input
/**
* Default implementation of [InputJsonWriter.OnInputJsonWriterListener].
*
* @author [S. Grimault](mailto:sebastien.grimault@gmail.com)
*/
-class OnInputJsonWriterListenerImpl : InputJsonWriter.OnInputJsonWriterListener {
+class OnInputJsonWriterListenerImpl : InputJsonWriter.OnInputJsonWriterListener {
+
+ private val geoJsonWriter = GeoJsonWriter()
override fun writeAdditionalInputData(writer: JsonWriter,
- input: AbstractInput) {
+ input: Input) {
writeGeometry(writer,
input)
writeProperties(writer,
@@ -22,17 +25,22 @@ class OnInputJsonWriterListenerImpl : InputJsonWriter.OnInputJsonWriterListener
}
private fun writeGeometry(writer: JsonWriter,
- input: AbstractInput) {
+ input: Input) {
writer.name("geometry")
- .beginObject()
- // TODO: write geometry object
+ val geometry = input.geometry
- writer.endObject()
+ if (geometry == null) {
+ writer.nullValue()
+ }
+ else {
+ geoJsonWriter.writeGeometry(writer,
+ geometry)
+ }
}
private fun writeProperties(writer: JsonWriter,
- input: AbstractInput) {
+ input: Input) {
writer.name("properties")
.beginObject()
@@ -54,7 +62,7 @@ class OnInputJsonWriterListenerImpl : InputJsonWriter.OnInputJsonWriterListener
}
private fun writeDate(writer: JsonWriter,
- input: AbstractInput) {
+ input: Input) {
val dateToIsoString = IsoDateUtils.toIsoDateString(input.date)
writer.name("date_min")
.value(dateToIsoString)
@@ -63,7 +71,7 @@ class OnInputJsonWriterListenerImpl : InputJsonWriter.OnInputJsonWriterListener
}
private fun writeInputObserverIds(writer: JsonWriter,
- input: AbstractInput) {
+ input: Input) {
writer.name("observers")
.beginArray()
@@ -74,7 +82,7 @@ class OnInputJsonWriterListenerImpl : InputJsonWriter.OnInputJsonWriterListener
}
private fun writeInputTaxa(writer: JsonWriter,
- input: AbstractInput) {
+ input: Input) {
writer.name("t_occurrences_occtax")
.beginArray()
diff --git a/occtax/src/main/java/fr/geonature/occtax/settings/AppSettings.kt b/occtax/src/main/java/fr/geonature/occtax/settings/AppSettings.kt
new file mode 100644
index 00000000..0b744caa
--- /dev/null
+++ b/occtax/src/main/java/fr/geonature/occtax/settings/AppSettings.kt
@@ -0,0 +1,53 @@
+package fr.geonature.occtax.settings
+
+import android.os.Parcel
+import android.os.Parcelable
+import fr.geonature.commons.settings.IAppSettings
+import fr.geonature.maps.settings.MapSettings
+
+/**
+ * Global internal settings.
+ *
+ * @author [S. Grimault](mailto:sebastien.grimault@gmail.com)
+ */
+data class AppSettings(var mapSettings: MapSettings? = null) : IAppSettings {
+
+ private constructor(source: Parcel) : this(source.readParcelable(MapSettings::class.java.classLoader) as MapSettings)
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ override fun writeToParcel(dest: Parcel?,
+ flags: Int) {
+ dest?.writeParcelable(
+ mapSettings,
+ 0
+ )
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as AppSettings
+
+ if (mapSettings != other.mapSettings) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return mapSettings.hashCode()
+ }
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): AppSettings {
+ return AppSettings(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+}
\ No newline at end of file
diff --git a/occtax/src/main/java/fr/geonature/occtax/settings/io/OnAppSettingsJsonReaderListenerImpl.kt b/occtax/src/main/java/fr/geonature/occtax/settings/io/OnAppSettingsJsonReaderListenerImpl.kt
new file mode 100644
index 00000000..355be54f
--- /dev/null
+++ b/occtax/src/main/java/fr/geonature/occtax/settings/io/OnAppSettingsJsonReaderListenerImpl.kt
@@ -0,0 +1,40 @@
+package fr.geonature.occtax.settings.io
+
+import android.util.JsonReader
+import android.util.JsonToken
+import fr.geonature.commons.settings.io.AppSettingsJsonReader
+import fr.geonature.maps.settings.io.MapSettingsReader
+import fr.geonature.occtax.settings.AppSettings
+
+/**
+ * Default implementation of [AppSettingsJsonReader.OnAppSettingsJsonReaderListener].
+ *
+ * @author [S. Grimault](mailto:sebastien.grimault@gmail.com)
+ */
+class OnAppSettingsJsonReaderListenerImpl : AppSettingsJsonReader.OnAppSettingsJsonReaderListener {
+
+ override fun createAppSettings(): AppSettings {
+ return AppSettings()
+ }
+
+ override fun readAdditionalAppSettingsData(reader: JsonReader,
+ keyName: String,
+ appSettings: AppSettings) {
+ when (keyName) {
+ "map" -> {
+ if (reader.peek() == JsonToken.BEGIN_OBJECT) {
+ readMapSettings(reader,
+ appSettings)
+ }
+ else {
+ reader.skipValue()
+ }
+ }
+ }
+ }
+
+ private fun readMapSettings(reader: JsonReader,
+ appSettings: AppSettings) {
+ appSettings.mapSettings = MapSettingsReader().read(reader)
+ }
+}
\ No newline at end of file
diff --git a/occtax/src/main/java/fr/geonature/occtax/ui/home/HomeActivity.kt b/occtax/src/main/java/fr/geonature/occtax/ui/home/HomeActivity.kt
index f27df65e..532b1949 100644
--- a/occtax/src/main/java/fr/geonature/occtax/ui/home/HomeActivity.kt
+++ b/occtax/src/main/java/fr/geonature/occtax/ui/home/HomeActivity.kt
@@ -2,6 +2,13 @@ package fr.geonature.occtax.ui.home
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
+import fr.geonature.commons.input.InputManager
+import fr.geonature.commons.settings.AppSettingsManager
+import fr.geonature.occtax.input.Input
+import fr.geonature.occtax.input.io.OnInputJsonReaderListenerImpl
+import fr.geonature.occtax.input.io.OnInputJsonWriterListenerImpl
+import fr.geonature.occtax.settings.AppSettings
+import fr.geonature.occtax.settings.io.OnAppSettingsJsonReaderListenerImpl
import fr.geonature.occtax.ui.input.InputPagerFragmentActivity
import fr.geonature.occtax.ui.settings.PreferencesActivity
import fr.geonature.occtax.util.IntentUtils
@@ -14,19 +21,36 @@ import fr.geonature.occtax.util.IntentUtils
* @author [S. Grimault](mailto:sebastien.grimault@gmail.com)
*/
class HomeActivity : AppCompatActivity(),
- HomeFragment.OnHomeFragmentFragmentListener {
+ HomeFragment.OnHomeFragmentListener {
+
+ private lateinit var inputManager: InputManager
+ private lateinit var appSettingsManager: AppSettingsManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ inputManager = InputManager(application,
+ OnInputJsonReaderListenerImpl(),
+ OnInputJsonWriterListenerImpl())
+ appSettingsManager = AppSettingsManager(application,
+ OnAppSettingsJsonReaderListenerImpl())
+
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
- .replace(android.R.id.content,
- HomeFragment.newInstance())
- .commit()
+ .replace(android.R.id.content,
+ HomeFragment.newInstance())
+ .commit()
}
}
+ override fun getInputManager(): InputManager {
+ return inputManager
+ }
+
+ override fun getAppSettingsManager(): AppSettingsManager {
+ return appSettingsManager
+ }
+
override fun onShowSettings() {
startActivity(PreferencesActivity.newIntent(this))
}
@@ -35,7 +59,10 @@ class HomeActivity : AppCompatActivity(),
startActivity(IntentUtils.syncActivity(this))
}
- override fun onStartInput() {
- startActivity(InputPagerFragmentActivity.newIntent(this))
+ override fun onStartInput(appSettings: AppSettings,
+ input: Input?) {
+ startActivity(InputPagerFragmentActivity.newIntent(this,
+ appSettings,
+ input))
}
}
diff --git a/occtax/src/main/java/fr/geonature/occtax/ui/home/HomeFragment.kt b/occtax/src/main/java/fr/geonature/occtax/ui/home/HomeFragment.kt
index 89c19f5e..d8f9c1c2 100644
--- a/occtax/src/main/java/fr/geonature/occtax/ui/home/HomeFragment.kt
+++ b/occtax/src/main/java/fr/geonature/occtax/ui/home/HomeFragment.kt
@@ -1,23 +1,47 @@
package fr.geonature.occtax.ui.home
+import android.Manifest
import android.content.Context
import android.database.Cursor
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
+import android.view.animation.AnimationUtils
import androidx.core.os.bundleOf
+import androidx.core.util.Pair
import androidx.fragment.app.Fragment
import androidx.loader.app.LoaderManager
import androidx.loader.content.CursorLoader
import androidx.loader.content.Loader
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.snackbar.Snackbar
import fr.geonature.commons.data.AppSync
import fr.geonature.commons.data.Provider.buildUri
-import fr.geonature.occtax.ui.settings.PreferencesFragment
+import fr.geonature.commons.input.InputManager
+import fr.geonature.commons.settings.AppSettingsManager
+import fr.geonature.commons.util.PermissionUtils.OnCheckSelfPermissionListener
+import fr.geonature.commons.util.PermissionUtils.checkPermissions
+import fr.geonature.commons.util.PermissionUtils.checkSelfPermissions
+import fr.geonature.commons.util.PermissionUtils.requestPermissions
+import fr.geonature.occtax.R
+import fr.geonature.occtax.input.Input
+import fr.geonature.occtax.settings.AppSettings
import fr.geonature.occtax.ui.shared.view.ListItemActionView
+import kotlinx.android.synthetic.main.fragment_home.appSyncView
+import kotlinx.android.synthetic.main.fragment_home.fab
+import kotlinx.android.synthetic.main.fragment_home.homeContent
+import kotlinx.android.synthetic.main.fragment_home.inputEmptyTextView
+import kotlinx.android.synthetic.main.fragment_home.inputRecyclerView
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
/**
* Home screen [Fragment].
@@ -26,8 +50,11 @@ import fr.geonature.occtax.ui.shared.view.ListItemActionView
*/
class HomeFragment : Fragment() {
- private var listener: OnHomeFragmentFragmentListener? = null
- private lateinit var appSyncView: AppSyncView
+ private var listener: OnHomeFragmentListener? = null
+ private lateinit var adapter: InputRecyclerViewAdapter
+ private var appSettings: AppSettings? = null
+ private var selectedInputToDelete: Pair? = null
+ private var requestPermissionsResult = true
private val loaderCallbacks = object : LoaderManager.LoaderCallbacks {
override fun onCreateLoader(
@@ -36,7 +63,8 @@ class HomeFragment : Fragment() {
when (id) {
LOADER_APP_SYNC -> return CursorLoader(requireContext(),
buildUri(AppSync.TABLE_NAME,
- args!!.getString(AppSync.COLUMN_ID)!!),
+ args?.getString(AppSync.COLUMN_ID)
+ ?: ""),
arrayOf(AppSync.COLUMN_ID,
AppSync.COLUMN_LAST_SYNC,
AppSync.COLUMN_INPUTS_TO_SYNCHRONIZE),
@@ -51,7 +79,12 @@ class HomeFragment : Fragment() {
loader: Loader,
data: Cursor?) {
- if (data == null) return
+ if (data == null) {
+ Log.w(TAG,
+ "Failed to load data from '${(loader as CursorLoader).uri}'")
+
+ return
+ }
when (loader.id) {
LOADER_APP_SYNC -> {
@@ -67,11 +100,23 @@ class HomeFragment : Fragment() {
}
}
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val selectedInputPositionToDelete = savedInstanceState?.getInt(STATE_SELECTED_INPUT_POSITION_TO_DELETE)
+ val selectedInputToDelete = savedInstanceState?.getParcelable(STATE_SELECTED_INPUT_TO_DELETE)
+
+ if (selectedInputPositionToDelete != null && selectedInputToDelete != null) {
+ this.selectedInputToDelete = Pair.create(selectedInputPositionToDelete,
+ selectedInputToDelete)
+ }
+ }
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View {
- return inflater.inflate(fr.geonature.occtax.R.layout.home_fragment,
+ return inflater.inflate(R.layout.fragment_home,
container,
false)
}
@@ -84,25 +129,130 @@ class HomeFragment : Fragment() {
setHasOptionsMenu(true)
- appSyncView = view.findViewById(fr.geonature.occtax.R.id.appSyncView)
appSyncView.setListener(object : ListItemActionView.OnListItemActionViewListener {
override fun onAction() {
listener?.onStartSync()
}
})
- view.findViewById(fr.geonature.occtax.R.id.fab)
- .setOnClickListener { listener?.onStartInput() }
+ fab.setOnClickListener {
+ val appSettings = appSettings ?: return@setOnClickListener
+ listener?.onStartInput(appSettings)
+ }
+
+ adapter = InputRecyclerViewAdapter(object : InputRecyclerViewAdapter.OnInputRecyclerViewAdapterListener {
+ override fun onInputClicked(input: Input) {
+ val appSettings = appSettings ?: return
+
+ Log.i(TAG,
+ "input selected: ${input.id}")
+
+ listener?.onStartInput(appSettings,
+ input)
+ }
+
+ override fun onInputLongClicked(position: Int,
+ input: Input) {
+ selectedInputToDelete = Pair.create(position,
+ input)
+
+ GlobalScope.launch(Dispatchers.Main) {
+ listener?.getInputManager()
+ ?.deleteInput(input.id)
+ (inputRecyclerView.adapter as InputRecyclerViewAdapter).remove(input)
+ }
+
+ Snackbar.make(homeContent,
+ R.string.home_snackbar_input_deleted,
+ Snackbar.LENGTH_SHORT)
+ .setAction(R.string.home_snackbar_input_undo
+ ) {
+ GlobalScope.launch(Dispatchers.Main) {
+ val inputToRestore = selectedInputToDelete?.second
+
+ if (inputToRestore != null) {
+ listener?.getInputManager()
+ ?.saveInput(inputToRestore)
+ (inputRecyclerView.adapter as InputRecyclerViewAdapter).addInput(inputToRestore,
+ selectedInputToDelete?.first)
+ }
+
+ selectedInputToDelete = null
+ }
+ }
+ .show()
+ }
+ })
+ adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
+ override fun onChanged() {
+ super.onChanged()
+
+ showEmptyTextView(adapter.itemCount == 0)
+ }
+
+ override fun onItemRangeChanged(positionStart: Int,
+ itemCount: Int) {
+ super.onItemRangeChanged(positionStart,
+ itemCount)
+
+ showEmptyTextView(adapter.itemCount == 0)
+ }
+
+ override fun onItemRangeInserted(positionStart: Int,
+ itemCount: Int) {
+ super.onItemRangeInserted(positionStart,
+ itemCount)
+
+ showEmptyTextView(false)
+ }
+ })
+
+ with(inputRecyclerView) {
+ layoutManager = LinearLayoutManager(context)
+ adapter = this@HomeFragment.adapter
+ }
+
+ val dividerItemDecoration = DividerItemDecoration(inputRecyclerView.context,
+ (inputRecyclerView.layoutManager as LinearLayoutManager).orientation)
+ inputRecyclerView.addItemDecoration(dividerItemDecoration)
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ LoaderManager.getInstance(this)
+ .initLoader(LOADER_APP_SYNC,
+ bundleOf(AppSync.COLUMN_ID to requireContext().packageName),
+ loaderCallbacks)
+
+ if (requestPermissionsResult) {
+ val context = context ?: return
+ checkSelfPermissions(context,
+ object : OnCheckSelfPermissionListener {
+ override fun onPermissionsGranted() {
+ loadAppSettings()
+ }
+
+ override fun onRequestPermissions(vararg permissions: String) {
+ requestPermissions(this@HomeFragment,
+ homeContent,
+ R.string.snackbar_permission_external_storage_rationale,
+ REQUEST_EXTERNAL_STORAGE,
+ *permissions)
+ }
+ },
+ Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ }
}
override fun onAttach(context: Context) {
super.onAttach(context)
- if (context is OnHomeFragmentFragmentListener) {
+ if (context is OnHomeFragmentListener) {
listener = context
}
else {
- throw RuntimeException("$context must implement OnHomeFragmentFragmentListener")
+ throw RuntimeException("$context must implement OnHomeFragmentListener")
}
}
@@ -112,28 +262,40 @@ class HomeFragment : Fragment() {
listener = null
}
+ override fun onSaveInstanceState(outState: Bundle) {
+ val selectedInputPositionToDelete = this.selectedInputToDelete?.first
+ val selectedInputToDelete = this.selectedInputToDelete?.second
+
+ if (selectedInputPositionToDelete != null && selectedInputToDelete != null) {
+ outState.putInt(STATE_SELECTED_INPUT_POSITION_TO_DELETE,
+ selectedInputPositionToDelete)
+ outState.putParcelable(STATE_SELECTED_INPUT_TO_DELETE,
+ selectedInputToDelete)
+ }
+
+ super.onSaveInstanceState(outState)
+ }
+
override fun onCreateOptionsMenu(
menu: Menu,
inflater: MenuInflater) {
super.onCreateOptionsMenu(menu,
inflater)
- inflater.inflate(fr.geonature.occtax.R.menu.settings,
+ inflater.inflate(R.menu.settings,
menu)
}
- /*
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
val menuItemSettings = menu.findItem(R.id.menu_settings)
- menuItemSettings.isEnabled = true
+ menuItemSettings.isEnabled = appSettings != null
}
- */
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
- fr.geonature.occtax.R.id.menu_settings -> {
+ R.id.menu_settings -> {
listener?.onShowSettings()
true
}
@@ -141,32 +303,102 @@ class HomeFragment : Fragment() {
}
}
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
+ override fun onRequestPermissionsResult(requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray) {
+ when (requestCode) {
+ REQUEST_EXTERNAL_STORAGE -> {
+ requestPermissionsResult = checkPermissions(grantResults)
- LoaderManager.getInstance(this)
- .initLoader(LOADER_APP_SYNC,
- bundleOf(AppSync.COLUMN_ID to requireContext().packageName),
- loaderCallbacks)
+ if (requestPermissionsResult) {
+ Snackbar.make(homeContent,
+ R.string.snackbar_permission_external_storage_available,
+ Snackbar.LENGTH_LONG)
+ .show()
+ }
+ else {
+ Snackbar.make(homeContent,
+ R.string.snackbar_permissions_not_granted,
+ Snackbar.LENGTH_LONG)
+ .show()
+ }
+ }
+ else -> super.onRequestPermissionsResult(requestCode,
+ permissions,
+ grantResults)
+ }
+ }
+
+ private fun loadAppSettings() {
+ GlobalScope.launch(Dispatchers.Main) {
+ appSettings = listener?.getAppSettingsManager()
+ ?.loadAppSettings()
+
+ if (appSettings == null) {
+ fab.hide()
+ adapter.clear()
+ activity?.invalidateOptionsMenu()
+
+ Snackbar.make(homeContent,
+ getString(R.string.snackbar_settings_not_found,
+ listener?.getAppSettingsManager()?.getAppSettingsFilename()),
+ Snackbar.LENGTH_LONG)
+ .show()
+ }
+ else {
+ fab.show()
+ activity?.invalidateOptionsMenu()
+
+ val inputs: List = listener?.getInputManager()?.readInputs()
+ ?: emptyList()
+ (inputRecyclerView.adapter as InputRecyclerViewAdapter).setInputs(inputs)
+ }
+ }
+ }
+
+ private fun showEmptyTextView(show: Boolean) {
+ if (inputEmptyTextView.visibility == View.VISIBLE == show) {
+ return
+ }
+
+ if (show) {
+ inputEmptyTextView.startAnimation(AnimationUtils.loadAnimation(context,
+ android.R.anim.fade_in))
+ inputEmptyTextView.visibility = View.VISIBLE
+
+ }
+ else {
+ inputEmptyTextView.startAnimation(AnimationUtils.loadAnimation(context,
+ android.R.anim.fade_out))
+ inputEmptyTextView.visibility = View.GONE
+ }
}
/**
- * Callback used by [PreferencesFragment].
+ * Callback used by [HomeFragment].
*/
- interface OnHomeFragmentFragmentListener {
+ interface OnHomeFragmentListener {
+ fun getInputManager(): InputManager
+ fun getAppSettingsManager(): AppSettingsManager
fun onShowSettings()
fun onStartSync()
- fun onStartInput()
+ fun onStartInput(appSettings: AppSettings,
+ input: Input? = null)
}
companion object {
+ private val TAG = HomeFragment::class.java.name
private const val LOADER_APP_SYNC = 1
+ private const val STATE_SELECTED_INPUT_POSITION_TO_DELETE = "state_selected_input_position_to_delete"
+ private const val STATE_SELECTED_INPUT_TO_DELETE = "state_selected_input_to_delete"
+ private const val REQUEST_EXTERNAL_STORAGE = 0
/**
* Use this factory method to create a new instance of [HomeFragment].
*
* @return A new instance of [HomeFragment]
*/
+ @JvmStatic
fun newInstance() = HomeFragment()
}
}
diff --git a/occtax/src/main/java/fr/geonature/occtax/ui/home/InputRecyclerViewAdapter.kt b/occtax/src/main/java/fr/geonature/occtax/ui/home/InputRecyclerViewAdapter.kt
new file mode 100644
index 00000000..e8380142
--- /dev/null
+++ b/occtax/src/main/java/fr/geonature/occtax/ui/home/InputRecyclerViewAdapter.kt
@@ -0,0 +1,107 @@
+package fr.geonature.occtax.ui.home
+
+import android.text.format.DateFormat
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import fr.geonature.occtax.R
+import fr.geonature.occtax.input.Input
+
+/**
+ * @author [S. Grimault](mailto:sebastien.grimault@gmail.com)
+ */
+class InputRecyclerViewAdapter(private val listener: OnInputRecyclerViewAdapterListener) : RecyclerView.Adapter() {
+ private val inputs: MutableList = mutableListOf()
+
+ override fun onCreateViewHolder(parent: ViewGroup,
+ viewType: Int): ViewHolder {
+ return ViewHolder(parent)
+ }
+
+ override fun getItemCount(): Int {
+ return inputs.size
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder,
+ position: Int) {
+ holder.bind(inputs[position])
+ }
+
+ fun setInputs(inputs: List) {
+ this.inputs.clear()
+ this.inputs.addAll(inputs)
+
+ notifyDataSetChanged()
+ }
+
+ fun addInput(input: Input,
+ index: Int? = null) {
+ if (index == null) {
+ this.inputs.add(input)
+ notifyItemRangeInserted(this.inputs.size - 1,
+ 1)
+ }
+ else {
+ this.inputs.add(index,
+ input)
+ notifyItemRangeChanged(index,
+ this.inputs.size - index)
+ }
+ }
+
+ fun clear() {
+ this.inputs.clear()
+ notifyDataSetChanged()
+ }
+
+ fun remove(input: Input) {
+ val inputPosition = this.inputs.indexOf(input)
+ this.inputs.remove(input)
+ notifyItemRemoved(inputPosition)
+
+ if (this.inputs.isEmpty()) {
+ notifyDataSetChanged()
+ }
+ }
+
+ inner class ViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item_2,
+ parent,
+ false)) {
+
+ private val text1: TextView = itemView.findViewById(android.R.id.text1)
+
+ fun bind(input: Input) {
+ text1.text = itemView.context.getString(R.string.home_input_created_at,
+ DateFormat.format(itemView.context.getString(R.string.home_input_date),
+ input.date))
+ itemView.setOnClickListener { listener.onInputClicked(input) }
+ itemView.setOnLongClickListener {
+ listener.onInputLongClicked(inputs.indexOf(input),
+ input)
+ true
+ }
+ }
+ }
+
+ /**
+ * Callback used by [InputRecyclerViewAdapter].
+ */
+ interface OnInputRecyclerViewAdapterListener {
+
+ /**
+ * Called when a [Input] has been clicked.
+ *
+ * @param input the selected [Input]
+ */
+ fun onInputClicked(input: Input)
+
+ /**
+ * Called when a [Input] has been clicked and held.
+ *
+ * @param input the selected [Input]
+ */
+ fun onInputLongClicked(position: Int,
+ input: Input)
+ }
+}
\ No newline at end of file
diff --git a/occtax/src/main/java/fr/geonature/occtax/ui/input/InputPagerFragmentActivity.kt b/occtax/src/main/java/fr/geonature/occtax/ui/input/InputPagerFragmentActivity.kt
index 60b135ea..c7acba01 100644
--- a/occtax/src/main/java/fr/geonature/occtax/ui/input/InputPagerFragmentActivity.kt
+++ b/occtax/src/main/java/fr/geonature/occtax/ui/input/InputPagerFragmentActivity.kt
@@ -10,6 +10,8 @@ import fr.geonature.occtax.R
import fr.geonature.occtax.input.Input
import fr.geonature.occtax.input.io.OnInputJsonReaderListenerImpl
import fr.geonature.occtax.input.io.OnInputJsonWriterListenerImpl
+import fr.geonature.occtax.settings.AppSettings
+import fr.geonature.occtax.ui.input.map.InputMapFragment
import fr.geonature.occtax.ui.input.observers.ObserversAndDateInputFragment
import fr.geonature.occtax.ui.input.taxa.TaxaFragment
import fr.geonature.viewpager.ui.AbstractNavigationHistoryPagerFragmentActivity
@@ -26,7 +28,8 @@ import kotlinx.coroutines.launch
*/
class InputPagerFragmentActivity : AbstractNavigationHistoryPagerFragmentActivity() {
- private lateinit var inputManager: InputManager
+ private lateinit var inputManager: InputManager
+ private lateinit var appSettings: AppSettings
private lateinit var input: Input
override fun onCreate(savedInstanceState: Bundle?) {
@@ -36,7 +39,20 @@ class InputPagerFragmentActivity : AbstractNavigationHistoryPagerFragmentActivit
OnInputJsonReaderListenerImpl(),
OnInputJsonWriterListenerImpl())
- readCurrentInput()
+ appSettings = intent.getParcelableExtra(EXTRA_APP_SETTINGS)
+ input = intent.getParcelableExtra(EXTRA_INPUT) ?: Input()
+ val lastAddedInputTaxon = input.getLastAddedInputTaxon()
+
+ if (lastAddedInputTaxon != null) {
+ input.setCurrentSelectedInputTaxonId(lastAddedInputTaxon.id)
+ }
+
+ Log.i(TAG,
+ "loading input: ${input.id}")
+
+ GlobalScope.launch(Dispatchers.Main) {
+ pagerManager.load(input.id)
+ }
}
override fun onPause() {
@@ -51,6 +67,8 @@ class InputPagerFragmentActivity : AbstractNavigationHistoryPagerFragmentActivit
get() = LinkedHashMap().apply {
put(R.string.pager_fragment_observers_and_date_input_title,
ObserversAndDateInputFragment.newInstance())
+ put(R.string.pager_fragment_map_title,
+ InputMapFragment.newInstance(appSettings.mapSettings!!))
put(R.string.pager_fragment_taxa_title,
TaxaFragment.newInstance())
}
@@ -69,19 +87,6 @@ class InputPagerFragmentActivity : AbstractNavigationHistoryPagerFragmentActivit
setInputToCurrentPage()
}
- private fun readCurrentInput() {
- GlobalScope.launch(Dispatchers.Main) {
- val input = inputManager.readCurrentInput() ?: Input()
-
- Log.i(TAG, "loading input: ${input.id}")
-
- pagerHelper.load(input.id)
-
- this@InputPagerFragmentActivity.input = input as Input
- this@InputPagerFragmentActivity.setInputToCurrentPage()
- }
- }
-
private fun setInputToCurrentPage() {
val pageFragment = getCurrentPageFragment()
@@ -96,9 +101,19 @@ class InputPagerFragmentActivity : AbstractNavigationHistoryPagerFragmentActivit
private val TAG = InputPagerFragmentActivity::class.java.name
- fun newIntent(context: Context): Intent {
+ private const val EXTRA_APP_SETTINGS = "extra_app_settings"
+ private const val EXTRA_INPUT = "extra_input"
+
+ fun newIntent(context: Context,
+ appSettings: AppSettings,
+ input: Input? = null): Intent {
return Intent(context,
- InputPagerFragmentActivity::class.java)
+ InputPagerFragmentActivity::class.java).apply {
+ putExtra(EXTRA_APP_SETTINGS,
+ appSettings)
+ putExtra(EXTRA_INPUT,
+ input)
+ }
}
}
}
diff --git a/occtax/src/main/java/fr/geonature/occtax/ui/input/map/InputMapFragment.kt b/occtax/src/main/java/fr/geonature/occtax/ui/input/map/InputMapFragment.kt
new file mode 100644
index 00000000..4fa94575
--- /dev/null
+++ b/occtax/src/main/java/fr/geonature/occtax/ui/input/map/InputMapFragment.kt
@@ -0,0 +1,90 @@
+package fr.geonature.occtax.ui.input.map
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import fr.geonature.commons.input.AbstractInput
+import fr.geonature.maps.jts.geojson.GeometryUtils.fromPoint
+import fr.geonature.maps.jts.geojson.GeometryUtils.toPoint
+import fr.geonature.maps.settings.MapSettings
+import fr.geonature.maps.ui.MapFragment
+import fr.geonature.maps.ui.widget.EditFeatureButton
+import fr.geonature.occtax.R
+import fr.geonature.occtax.input.Input
+import fr.geonature.occtax.ui.input.IInputFragment
+import fr.geonature.viewpager.ui.AbstractPagerFragmentActivity
+import fr.geonature.viewpager.ui.IValidateFragment
+import org.locationtech.jts.geom.Point
+import org.osmdroid.util.GeoPoint
+import org.osmdroid.views.MapView
+
+/**
+ * Simple [Fragment] embedding a [MapView] instance to edit a single POI on the map.
+ *
+ * @author [S. Grimault](mailto:sebastien.grimault@gmail.com)
+ */
+class InputMapFragment : MapFragment(),
+ IValidateFragment,
+ IInputFragment {
+
+ private var input: Input? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ onSelectedPOIsListener = object : OnSelectedPOIsListener {
+ override fun onSelectedPOIs(pois: List) {
+ if (pois.isNotEmpty()) {
+ input?.geometry = toPoint(pois[0])
+ }
+
+ (activity as AbstractPagerFragmentActivity?)?.validateCurrentPage()
+ }
+ }
+ }
+
+ override fun getResourceTitle(): Int {
+ return R.string.pager_fragment_map_title
+ }
+
+ override fun pagingEnabled(): Boolean {
+ return false
+ }
+
+ override fun validate(): Boolean {
+ return this.input?.geometry != null
+ }
+
+ override fun refreshView() {
+ val geometry = input?.geometry ?: return
+
+ if (geometry is Point) {
+ setSelectedPOIs(listOf(fromPoint(geometry)))
+ }
+ }
+
+ override fun setInput(input: AbstractInput) {
+ this.input = input as Input
+ }
+
+ companion object {
+
+ /**
+ * Use this factory method to create a new instance of [InputMapFragment].
+ *
+ * @return A new instance of [InputMapFragment]
+ */
+ @JvmStatic
+ fun newInstance(mapSettings: MapSettings) = InputMapFragment().apply {
+ arguments = Bundle().apply {
+ putParcelable(
+ ARG_MAP_SETTINGS,
+ mapSettings
+ )
+ putSerializable(
+ ARG_EDIT_MODE,
+ EditFeatureButton.EditMode.SINGLE
+ )
+ }
+ }
+ }
+}
diff --git a/occtax/src/main/java/fr/geonature/occtax/ui/input/observers/ObserversAndDateInputFragment.kt b/occtax/src/main/java/fr/geonature/occtax/ui/input/observers/ObserversAndDateInputFragment.kt
index ad0c25a1..59fd27a0 100644
--- a/occtax/src/main/java/fr/geonature/occtax/ui/input/observers/ObserversAndDateInputFragment.kt
+++ b/occtax/src/main/java/fr/geonature/occtax/ui/input/observers/ObserversAndDateInputFragment.kt
@@ -5,6 +5,7 @@ import android.content.Intent
import android.database.Cursor
import android.os.Bundle
import android.text.format.DateFormat
+import android.util.Log
import android.util.Pair
import android.view.LayoutInflater
import android.view.View
@@ -71,7 +72,11 @@ class ObserversAndDateInputFragment : Fragment(),
loader: Loader,
data: Cursor?) {
- if (data == null) return
+ if (data == null) {
+ Log.w(TAG, "Failed to load data from '${(loader as CursorLoader).uri}'")
+
+ return
+ }
when (loader.id) {
LOADER_OBSERVERS_IDS -> {
@@ -221,6 +226,7 @@ class ObserversAndDateInputFragment : Fragment(),
companion object {
+ private val TAG = ObserversAndDateInputFragment::class.java.name
private const val DATE_PICKER_DIALOG_FRAGMENT = "date_picker_dialog_fragment"
private const val LOADER_OBSERVERS_IDS = 1
private const val KEY_SELECTED_INPUT_OBSERVER_IDS = "selected_input_observer_ids"
diff --git a/occtax/src/main/java/fr/geonature/occtax/ui/input/taxa/TaxaFragment.kt b/occtax/src/main/java/fr/geonature/occtax/ui/input/taxa/TaxaFragment.kt
index 66ec0214..b742c04a 100644
--- a/occtax/src/main/java/fr/geonature/occtax/ui/input/taxa/TaxaFragment.kt
+++ b/occtax/src/main/java/fr/geonature/occtax/ui/input/taxa/TaxaFragment.kt
@@ -2,6 +2,7 @@ package fr.geonature.occtax.ui.input.taxa
import android.database.Cursor
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
@@ -86,7 +87,11 @@ class TaxaFragment : Fragment(),
override fun onLoadFinished(loader: Loader,
data: Cursor?) {
- if (data == null) return
+ if (data == null) {
+ Log.w(TAG, "Failed to load data from '${(loader as CursorLoader).uri}'")
+
+ return
+ }
when (loader.id) {
LOADER_TAXA -> adapter?.bind(data)
@@ -250,6 +255,7 @@ class TaxaFragment : Fragment(),
companion object {
+ private val TAG = TaxaFragment::class.java.name
private const val LOADER_TAXA = 1
private const val LOADER_TAXON = 2
private const val KEY_FILTER = "filter"
diff --git a/occtax/src/main/java/fr/geonature/occtax/ui/input/taxa/TaxaRecyclerViewAdapter.kt b/occtax/src/main/java/fr/geonature/occtax/ui/input/taxa/TaxaRecyclerViewAdapter.kt
index 90c6a427..a760d537 100644
--- a/occtax/src/main/java/fr/geonature/occtax/ui/input/taxa/TaxaRecyclerViewAdapter.kt
+++ b/occtax/src/main/java/fr/geonature/occtax/ui/input/taxa/TaxaRecyclerViewAdapter.kt
@@ -70,11 +70,12 @@ class TaxaRecyclerViewAdapter(private val listener: OnTaxaRecyclerViewAdapterLis
val name = taxon.name ?: return ""
return name.elementAt(0)
- .toString()
+ .toString()
}
fun setSelectedTaxon(selectedTaxon: Taxon) {
this.selectedTaxon = selectedTaxon
+ scrollToFirstItemSelected()
notifyDataSetChanged()
}
@@ -119,7 +120,7 @@ class TaxaRecyclerViewAdapter(private val listener: OnTaxaRecyclerViewAdapterLis
}
}
- inner class ViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_title_taxon,
+ inner class ViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_title_item_2,
parent,
false)) {
@@ -138,8 +139,8 @@ class TaxaRecyclerViewAdapter(private val listener: OnTaxaRecyclerViewAdapterLis
val previousTitle = if (position > 0) {
cursor.moveToPosition(position - 1)
Taxon.fromCursor(cursor)
- ?.name?.elementAt(0)
- .toString()
+ ?.name?.elementAt(0)
+ .toString()
}
else {
""
@@ -147,7 +148,7 @@ class TaxaRecyclerViewAdapter(private val listener: OnTaxaRecyclerViewAdapterLis
if (taxon != null) {
val currentTitle = taxon.name?.elementAt(0)
- .toString()
+ .toString()
title.text = if (previousTitle == currentTitle) "" else currentTitle
text1.text = taxon.name
text2.text = taxon.description
diff --git a/occtax/src/main/java/fr/geonature/occtax/ui/observers/InputObserverRecyclerViewAdapter.kt b/occtax/src/main/java/fr/geonature/occtax/ui/observers/InputObserverRecyclerViewAdapter.kt
index 447e48d5..5b5810df 100644
--- a/occtax/src/main/java/fr/geonature/occtax/ui/observers/InputObserverRecyclerViewAdapter.kt
+++ b/occtax/src/main/java/fr/geonature/occtax/ui/observers/InputObserverRecyclerViewAdapter.kt
@@ -30,12 +30,15 @@ class InputObserverRecyclerViewAdapter(private val listener: OnInputObserverRecy
val checkbox: CheckBox = v.findViewById(android.R.id.checkbox)
val inputObserver = v.tag as InputObserver
+ val isSelected = selectedInputObservers.contains(inputObserver)
if (isSingleChoice()) {
+ val selectedItemsPositions = selectedInputObservers.map { getItemPosition(it) }
selectedInputObservers.clear()
+ selectedItemsPositions.forEach { notifyItemChanged(it) }
}
- if (selectedInputObservers.contains(inputObserver)) {
+ if (isSelected) {
selectedInputObservers.remove(inputObserver)
checkbox.isChecked = false
}
@@ -72,7 +75,7 @@ class InputObserverRecyclerViewAdapter(private val listener: OnInputObserverRecy
val lastname = inputObserver.lastname ?: return ""
return lastname.elementAt(0)
- .toString()
+ .toString()
}
fun setChoiceMode(choiceMode: Int = ListView.CHOICE_MODE_SINGLE) {
@@ -99,6 +102,28 @@ class InputObserverRecyclerViewAdapter(private val listener: OnInputObserverRecy
notifyDataSetChanged()
}
+ private fun getItemPosition(inputObserver: InputObserver?): Int {
+ var itemPosition = -1
+ val cursor = cursor ?: return itemPosition
+ if (inputObserver == null) return itemPosition
+
+ cursor.moveToFirst()
+
+ while (!cursor.isAfterLast && itemPosition < 0) {
+ val currentInputObserver = InputObserver.fromCursor(cursor)
+
+ if (inputObserver.id == currentInputObserver?.id) {
+ itemPosition = cursor.position
+ }
+
+ cursor.moveToNext()
+ }
+
+ cursor.moveToFirst()
+
+ return itemPosition
+ }
+
private fun scrollToFirstItemSelected() {
val cursor = cursor ?: return
@@ -142,8 +167,8 @@ class InputObserverRecyclerViewAdapter(private val listener: OnInputObserverRecy
val previousTitle = if (position > 0) {
cursor.moveToPosition(position - 1)
InputObserver.fromCursor(cursor)
- ?.lastname?.elementAt(0)
- .toString()
+ ?.lastname?.elementAt(0)
+ .toString()
}
else {
""
@@ -151,7 +176,7 @@ class InputObserverRecyclerViewAdapter(private val listener: OnInputObserverRecy
if (inputObserver != null) {
val currentTitle = inputObserver.lastname?.elementAt(0)
- .toString()
+ .toString()
title.text = if (previousTitle == currentTitle) "" else currentTitle
text1.text = inputObserver.lastname?.toUpperCase()
text2.text = inputObserver.firstname
diff --git a/occtax/src/main/res/layout/fragment_home.xml b/occtax/src/main/res/layout/fragment_home.xml
new file mode 100644
index 00000000..5ba0b166
--- /dev/null
+++ b/occtax/src/main/res/layout/fragment_home.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/occtax/src/main/res/layout/home_fragment.xml b/occtax/src/main/res/layout/home_fragment.xml
deleted file mode 100644
index d8f57518..00000000
--- a/occtax/src/main/res/layout/home_fragment.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/occtax/src/main/res/layout/list_title_taxon.xml b/occtax/src/main/res/layout/list_title_taxon.xml
deleted file mode 100644
index 0f227995..00000000
--- a/occtax/src/main/res/layout/list_title_taxon.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/occtax/src/main/res/values-fr/strings.xml b/occtax/src/main/res/values-fr/strings.xml
index 50d3a38e..2cba44a1 100644
--- a/occtax/src/main/res/values-fr/strings.xml
+++ b/occtax/src/main/res/values-fr/strings.xml
@@ -1,5 +1,5 @@
-
+
Paramètres
Observateurs
@@ -13,6 +13,10 @@
Relevés en cours
Aucun relevé en cours.\nCréer un nouveau relevé via le bouton +.
+ EEE dd MMM yyyy
+ Relevé créé le %s
+ Relevé supprimé
+ Annuler
Aucune donnée
Aucune sélection.\nAjouter en un via le bouton "Ajouter".
@@ -29,7 +33,14 @@
OK
Annuler
+ Erreur lors du chargement des paramètres \'%1$s\'
+ Les permissions n\'ont pas été accordées
+ Les permissions ont été accordées
+ L\'application requiert la permission d\'accéder au contenu de la mémoire de stockage
+ L\'accès à la mémoire de stockage a été accordée
+
Observateur & date
+ Pointage
Taxons
Observateurs
diff --git a/occtax/src/main/res/values/strings.xml b/occtax/src/main/res/values/strings.xml
index 899ce72b..b9f11bbe 100644
--- a/occtax/src/main/res/values/strings.xml
+++ b/occtax/src/main/res/values/strings.xml
@@ -17,6 +17,10 @@
Last inputs
No existing input.\nCreate a new one by tapping the + button.
+ EEE dd MMM yyyy
+ Input created at %s
+ Input deleted
+ Undo
No data
No selected item.\nAdd one by tapping the "Add" button.
@@ -33,7 +37,14 @@
OK
Cancel
+ Unable to load settings \'%1$s\'
+ Permissions were not granted
+ Permissions were been granted
+ External storage permission is needed
+ External storage Permissions have been granted
+
Observers & date
+ Pointing
Taxa
Selected observers
diff --git a/occtax/src/test/java/fr/geonature/occtax/input/io/InputJsonReaderTest.kt b/occtax/src/test/java/fr/geonature/occtax/input/io/InputJsonReaderTest.kt
index 116d351c..a49d29d5 100644
--- a/occtax/src/test/java/fr/geonature/occtax/input/io/InputJsonReaderTest.kt
+++ b/occtax/src/test/java/fr/geonature/occtax/input/io/InputJsonReaderTest.kt
@@ -23,7 +23,7 @@ import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class InputJsonReaderTest {
- private lateinit var inputJsonReader: InputJsonReader
+ private lateinit var inputJsonReader: InputJsonReader
@Before
fun setUp() {
@@ -108,6 +108,7 @@ class InputJsonReaderTest {
assertArrayEquals(longArrayOf(),
input.getInputObserverIds()
.toLongArray())
+ assertNull(input.geometry)
assertEquals(listOf(),
input.getInputTaxa())
}
diff --git a/occtax/src/test/java/fr/geonature/occtax/input/io/InputJsonWriterTest.kt b/occtax/src/test/java/fr/geonature/occtax/input/io/InputJsonWriterTest.kt
index f63b5a0d..fba474c2 100644
--- a/occtax/src/test/java/fr/geonature/occtax/input/io/InputJsonWriterTest.kt
+++ b/occtax/src/test/java/fr/geonature/occtax/input/io/InputJsonWriterTest.kt
@@ -21,7 +21,7 @@ import java.util.Date
@RunWith(RobolectricTestRunner::class)
class InputJsonWriterTest {
- private lateinit var inputJsonWriter: InputJsonWriter
+ private lateinit var inputJsonWriter: InputJsonWriter
@Before
fun setUp() {
diff --git a/occtax/src/test/java/fr/geonature/occtax/settings/io/AppSettingsJsonReaderTest.kt b/occtax/src/test/java/fr/geonature/occtax/settings/io/AppSettingsJsonReaderTest.kt
new file mode 100644
index 00000000..a7dc6cd8
--- /dev/null
+++ b/occtax/src/test/java/fr/geonature/occtax/settings/io/AppSettingsJsonReaderTest.kt
@@ -0,0 +1,97 @@
+package fr.geonature.occtax.settings.io
+
+import fr.geonature.commons.settings.io.AppSettingsJsonReader
+import fr.geonature.maps.settings.LayerSettings
+import fr.geonature.maps.settings.MapSettings
+import fr.geonature.occtax.FixtureHelper.getFixture
+import fr.geonature.occtax.settings.AppSettings
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.osmdroid.util.BoundingBox
+import org.osmdroid.util.GeoPoint
+import org.robolectric.RobolectricTestRunner
+
+/**
+ * Unit tests about [AppSettingsJsonReader].
+ *
+ * @author [S. Grimault](mailto:sebastien.grimault@gmail.com)
+ */
+@RunWith(RobolectricTestRunner::class)
+class AppSettingsJsonReaderTest {
+ lateinit var appSettingsJsonReader: AppSettingsJsonReader
+
+ @Before
+ fun setUp() {
+ appSettingsJsonReader = AppSettingsJsonReader(OnAppSettingsJsonReaderListenerImpl())
+ }
+
+ @Test
+ fun testReadAppSettingsFromJsonString() {
+ // given a JSON settings
+ val json = getFixture("settings_occtax.json")
+
+ // when read the JSON as AppSettings
+ val appSettings = appSettingsJsonReader.read(json)
+
+ // then
+ assertNotNull(appSettings)
+ assertEquals(
+ AppSettings(
+ MapSettings(
+ arrayListOf(
+ LayerSettings(
+ "Nantes",
+ "nantes.mbtiles"
+ )
+ ),
+ null,
+ showScale = true,
+ showCompass = true,
+ zoom = 10.0,
+ minZoomLevel = 8.0,
+ maxZoomLevel = 19.0,
+ minZoomEditing = 12.0,
+ maxBounds = BoundingBox.fromGeoPoints(
+ arrayListOf(
+ GeoPoint(
+ 47.253369,
+ -1.605721
+ ),
+ GeoPoint(
+ 47.173845,
+ -1.482811
+ )
+ )
+ ),
+ center = GeoPoint(
+ 47.225827,
+ -1.554470
+ )
+ )),
+ appSettings
+ )
+ }
+
+ @Test
+ fun testReadAppSettingsFromInvalidJsonString() {
+ // when read an invalid JSON as AppSettings
+ val appSettings = appSettingsJsonReader.read("")
+
+ // then
+ assertNull(appSettings)
+ }
+
+ @Test
+ fun testReadAppSettingsFromJsonStringWithNoMapSettings() {
+ // when read an empty JSON as AppSettings
+ val appSettings = appSettingsJsonReader.read("{\"map\":null}")
+
+ // then
+ assertNotNull(appSettings)
+ assertNull(appSettings?.mapSettings)
+ }
+}
\ No newline at end of file
diff --git a/occtax/src/test/resources/fixtures/input_no_observer_no_taxon.json b/occtax/src/test/resources/fixtures/input_no_observer_no_taxon.json
index fd02a715..93742e98 100644
--- a/occtax/src/test/resources/fixtures/input_no_observer_no_taxon.json
+++ b/occtax/src/test/resources/fixtures/input_no_observer_no_taxon.json
@@ -1,7 +1,7 @@
{
"id": 1234,
"module": "occtax",
- "geometry": {},
+ "geometry": null,
"properties": {
"meta_device_entry": "mobile",
"date_min": "2016-10-28T00:00:00Z",
diff --git a/occtax/src/test/resources/fixtures/input_simple.json b/occtax/src/test/resources/fixtures/input_simple.json
index efe55bd0..1fab43a7 100644
--- a/occtax/src/test/resources/fixtures/input_simple.json
+++ b/occtax/src/test/resources/fixtures/input_simple.json
@@ -1,7 +1,7 @@
{
"id": 1234,
"module": "occtax",
- "geometry": {},
+ "geometry": null,
"properties": {
"meta_device_entry": "mobile",
"date_min": "2016-10-28T00:00:00Z",
diff --git a/occtax/src/test/resources/fixtures/settings_occtax.json b/occtax/src/test/resources/fixtures/settings_occtax.json
new file mode 100644
index 00000000..09f603c1
--- /dev/null
+++ b/occtax/src/test/resources/fixtures/settings_occtax.json
@@ -0,0 +1,30 @@
+{
+ "map": {
+ "show_scale": true,
+ "show_compass": true,
+ "max_bounds": [
+ [
+ 47.253369,
+ -1.605721
+ ],
+ [
+ 47.173845,
+ -1.482811
+ ]
+ ],
+ "center": [
+ 47.225827,
+ -1.554470
+ ],
+ "start_zoom": 10.0,
+ "min_zoom": 8.0,
+ "max_zoom": 19.0,
+ "min_zoom_editing": 12.0,
+ "layers": [
+ {
+ "label": "Nantes",
+ "source": "nantes.mbtiles"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/occtax/version.properties b/occtax/version.properties
index 99810155..4b3b54b9 100644
--- a/occtax/version.properties
+++ b/occtax/version.properties
@@ -1,2 +1,2 @@
-#Wed Jun 05 22:37:37 CEST 2019
-VERSION_CODE=560
+#Sun Jul 14 17:10:50 CEST 2019
+VERSION_CODE=730
diff --git a/settings.gradle b/settings.gradle
index a4d14882..54d50263 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':commons', ':viewpager', ':occtax'
+include ':commons', ':viewpager', ':maps', ':occtax'
diff --git a/upgrade_submodules.sh b/upgrade_submodules.sh
index 202eda61..410b206c 100755
--- a/upgrade_submodules.sh
+++ b/upgrade_submodules.sh
@@ -3,11 +3,20 @@
APP_HOME="`pwd -P`"
cd $APP_HOME/commons
-git checkout -- version.properties
+git checkout -- .
+
cd $APP_HOME/viewpager
-git checkout -- version.properties
+git checkout -- .
+
+cd $APP_HOME/maps
+git checkout -- .
+
cd $APP_HOME/gn_mobile_core
git pull origin develop
+
+cd $APP_HOME/gn_mobile_maps
+git pull origin develop
+
cd $APP_HOME
git submodule update --remote
git submodule foreach git checkout develop