Skip to content

Commit 7487203

Browse files
committed
Merge branch 'release/1.3.0'
2 parents a35ea2c + 1b5c5c1 commit 7487203

File tree

98 files changed

+3259
-1734
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+3259
-1734
lines changed

.idea/codeStyles/Project.xml

-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Top-level build file where you can add configuration options common to all sub-projects/modules.
22

33
buildscript {
4-
ext.kotlin_version = '1.5.10'
4+
ext.kotlin_version = '1.5.21'
55

66
repositories {
77
google()
@@ -12,7 +12,7 @@ buildscript {
1212
}
1313

1414
dependencies {
15-
classpath 'com.android.tools.build:gradle:4.2.1'
15+
classpath 'com.android.tools.build:gradle:4.2.2'
1616
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1717
classpath "org.jlleitschuh.gradle:ktlint-gradle:9.4.1"
1818
// NOTE: Do not place any application dependencies here; they belong

commons/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
apply plugin: 'com.android.library'
22
apply plugin: 'kotlin-android'
33

4-
version = "0.8.4"
4+
version = "0.8.8"
55

66
android {
77
compileSdkVersion 29
@@ -54,7 +54,7 @@ dependencies {
5454
api 'androidx.room:room-runtime:2.3.0'
5555

5656
testImplementation 'androidx.arch.core:core-testing:2.1.0'
57-
testImplementation 'androidx.test:core:1.3.0'
57+
testImplementation 'androidx.test:core:1.4.0'
5858
testImplementation 'junit:junit:4.13.2'
5959
testImplementation 'org.mockito:mockito-core:3.0.0'
6060
testImplementation 'org.robolectric:robolectric:4.5.1'

commons/src/main/AndroidManifest.xml

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1-
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2-
package="fr.geonature.commons"
3-
android:sharedUserId="@string/sharedUserId">
1+
<manifest
2+
xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="fr.geonature.commons"
4+
android:sharedUserId="@string/sharedUserId">
45

5-
<uses-permission android:name="fr.geonature.sync.permission.READ"/>
6-
<permission android:name="fr.geonature.sync.permission.READ"/>
6+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
7+
<uses-permission android:name="fr.geonature.sync.permission.READ" />
8+
<uses-permission android:name="fr.geonature.sync.permission.WRITE" />
9+
10+
<permission
11+
android:name="fr.geonature.sync.permission.READ"
12+
android:protectionLevel="signature" />
13+
<permission
14+
android:name="fr.geonature.sync.permission.WRITE"
15+
android:protectionLevel="signature" />
716

817
</manifest>
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package fr.geonature.commons.data.helper
22

3-
import android.content.Context
43
import android.net.Uri
54
import android.net.Uri.withAppendedPath
6-
import fr.geonature.commons.R
75

86
/**
97
* Base content provider.
@@ -17,12 +15,6 @@ object Provider {
1715
*/
1816
const val AUTHORITY = "fr.geonature.sync.provider"
1917

20-
/**
21-
* Check if 'READ' permission is granted for content provider.
22-
*/
23-
val checkReadPermission: (Context, String?) -> Boolean =
24-
{ context, permission -> context.getString(R.string.permission_read) == permission }
25-
2618
/**
2719
* Build resource [Uri].
2820
*/
@@ -34,9 +26,10 @@ object Provider {
3426
val baseUri = Uri.parse("content://$AUTHORITY/$resource")
3527

3628
return if (path.isEmpty()) baseUri
37-
else withAppendedPath(
38-
baseUri,
39-
path.asSequence().filter { it.isNotBlank() }.joinToString("/")
40-
)
29+
else withAppendedPath(baseUri,
30+
path
31+
.asSequence()
32+
.filter { it.isNotBlank() }
33+
.joinToString("/"))
4134
}
4235
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package fr.geonature.commons.fp
2+
3+
/**
4+
* Represents a value of one of two possible types (a disjoint union).
5+
* Instances of [Either] are either an instance of [Left] or [Right].
6+
* FP Convention dictates that [Left] is used for "failure"
7+
* and [Right] is used for "success".
8+
*
9+
* @see Left
10+
* @see Right
11+
*/
12+
sealed class Either<out L, out R> {
13+
14+
/**
15+
* Represents the left side of [Either] class which by convention is a "Failure".
16+
*/
17+
data class Left<out L>(val a: L) : Either<L, Nothing>()
18+
19+
/**
20+
* Represents the right side of [Either] class which by convention is a "Success".
21+
*/
22+
data class Right<out R>(val b: R) : Either<Nothing, R>()
23+
24+
/**
25+
* Returns true if this is a [Right], false otherwise.
26+
* @see Right
27+
*/
28+
val isRight get() = this is Right<R>
29+
30+
/**
31+
* Returns true if this is a [Left], false otherwise.
32+
* @see Left
33+
*/
34+
val isLeft get() = this is Left<L>
35+
36+
/**
37+
* Creates a [Left] type.
38+
* @see Left
39+
*/
40+
fun <L> left(a: L) =
41+
Left(a)
42+
43+
/**
44+
* Creates a [Right] type.
45+
* @see Right
46+
*/
47+
fun <R> right(b: R) =
48+
Right(b)
49+
50+
/**
51+
* Applies fnL if this is a [Left] or fnR if this is a [Right].
52+
* @see Left
53+
* @see Right
54+
*/
55+
fun fold(
56+
fnL: (L) -> Any,
57+
fnR: (R) -> Any
58+
): Any =
59+
when (this) {
60+
is Left -> fnL(a)
61+
is Right -> fnR(b)
62+
}
63+
}
64+
65+
/**
66+
* Composes 2 functions.
67+
*
68+
* See [credits to Alex Hart.](https://proandroiddev.com/kotlins-nothing-type-946de7d464fb)
69+
*/
70+
fun <A, B, C> ((A) -> B).c(f: (B) -> C): (A) -> C =
71+
{
72+
f(this(it))
73+
}
74+
75+
/**
76+
* Right-biased flatMap() FP convention which means that [Either.Right] is assumed to be the default
77+
* case to operate on.
78+
* If it is [Either.Left], operations like map, flatMap, ... return the [Either.Left] value unchanged.
79+
*/
80+
fun <T, L, R> Either<L, R>.flatMap(fn: (R) -> Either<L, T>): Either<L, T> =
81+
when (this) {
82+
is Either.Left -> Either.Left(a)
83+
is Either.Right -> fn(b)
84+
}
85+
86+
/**
87+
* Right-biased map() FP convention which means that [Either.Right] is assumed to be the default case
88+
* to operate on.
89+
* If it is [Either.Left], operations like map, flatMap, ... return the [Either.Left] value unchanged.
90+
*/
91+
fun <T, L, R> Either<L, R>.map(fn: (R) -> (T)): Either<L, T> =
92+
this.flatMap(fn.c(::right))
93+
94+
/**
95+
* Returns the value from this [Either.Right] or the given argument if this is a [Either.Left].
96+
* Examples:
97+
* * `Right(12).getOrElse(17)` returns 12
98+
* * `Left(12).getOrElse(17)` returns 17
99+
*/
100+
fun <L, R> Either<L, R>.getOrElse(value: R): R =
101+
when (this) {
102+
is Either.Left -> value
103+
is Either.Right -> b
104+
}
105+
106+
/**
107+
* Left-biased onFailure() FP convention dictates that when this class is [Either.Left], it'll perform
108+
* the onFailure functionality passed as a parameter, but, overall will still return an either
109+
* object so you chain calls.
110+
*/
111+
fun <L, R> Either<L, R>.onFailure(fn: (failure: L) -> Unit): Either<L, R> =
112+
this.apply { if (this is Either.Left) fn(a) }
113+
114+
/**
115+
* Right-biased onSuccess() FP convention dictates that when this class is [Either.Right], it'll perform
116+
* the onSuccess functionality passed as a parameter, but, overall will still return an either
117+
* object so you chain calls.
118+
*/
119+
fun <L, R> Either<L, R>.onSuccess(fn: (success: R) -> Unit): Either<L, R> =
120+
this.apply { if (this is Either.Right) fn(b) }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package fr.geonature.commons.fp
2+
3+
/**
4+
* Base class for handling errors/failures/exceptions.
5+
* Every feature specific failure should extend [FeatureFailure] class.
6+
*/
7+
sealed class Failure {
8+
object NetworkFailure : Failure()
9+
object ServerFailure : Failure()
10+
11+
abstract class FeatureFailure : Failure()
12+
}

commons/src/main/java/fr/geonature/commons/input/AbstractInput.kt

+34-6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ abstract class AbstractInput(
2323

2424
var id: Long = generateId()
2525
var date: Date = Date()
26+
var status: Status = Status.DRAFT
2627
var datasetId: Long? = null
2728
private val inputObserverIds: MutableSet<Long> = mutableSetOf()
2829
private val inputTaxa: MutableMap<Long, AbstractInputTaxon> = LinkedHashMap()
@@ -31,7 +32,16 @@ abstract class AbstractInput(
3132
constructor(source: Parcel) : this(source.readString()!!) {
3233
this.id = source.readLong()
3334
this.date = source.readSerializable() as Date
34-
this.datasetId = source.readLong()
35+
this.status = source
36+
.readString()
37+
.let { statusAsString ->
38+
Status
39+
.values()
40+
.firstOrNull { it.name == statusAsString }
41+
?: Status.DRAFT
42+
}
43+
this.datasetId = source
44+
.readLong()
3545
.takeIf { it != -1L }
3646

3747
val inputObserverId = source.readLong()
@@ -61,9 +71,17 @@ abstract class AbstractInput(
6171
it.writeString(module)
6272
it.writeLong(this.id)
6373
it.writeSerializable(this.date)
64-
it.writeLong(this.datasetId ?: -1L)
74+
it.writeString(this.status.name)
75+
it.writeLong(
76+
this.datasetId
77+
?: -1L
78+
)
6579
it.writeLong(if (inputObserverIds.isEmpty()) -1 else inputObserverIds.first())
66-
it.writeLongArray(inputObserverIds.drop(1).toLongArray())
80+
it.writeLongArray(
81+
inputObserverIds
82+
.drop(1)
83+
.toLongArray()
84+
)
6785
it.writeTypedList(getInputTaxa())
6886
}
6987
}
@@ -77,6 +95,7 @@ abstract class AbstractInput(
7795
if (module != other.module) return false
7896
if (id != other.id) return false
7997
if (date != other.date) return false
98+
if (status != other.status) return false
8099
if (datasetId != other.datasetId) return false
81100
if (inputObserverIds != other.inputObserverIds) return false
82101
if (inputTaxa != other.inputTaxa) return false
@@ -88,6 +107,7 @@ abstract class AbstractInput(
88107
var result = module.hashCode()
89108
result = 31 * result + id.hashCode()
90109
result = 31 * result + date.hashCode()
110+
result = 31 * result + status.hashCode()
91111
result = 31 * result + datasetId.hashCode()
92112
result = 31 * result + inputObserverIds.hashCode()
93113
result = 31 * result + inputTaxa.hashCode()
@@ -96,7 +116,8 @@ abstract class AbstractInput(
96116
}
97117

98118
fun setDate(isoDate: String?) {
99-
this.date = toDate(isoDate) ?: Date()
119+
this.date = toDate(isoDate)
120+
?: Date()
100121
}
101122

102123
/**
@@ -117,7 +138,8 @@ abstract class AbstractInput(
117138
* Gets only selected input observers without the primary input observer.
118139
*/
119140
fun getInputObserverIds(): Set<Long> {
120-
return this.inputObserverIds.drop(1)
141+
return this.inputObserverIds
142+
.drop(1)
121143
.toSet()
122144
}
123145

@@ -126,7 +148,8 @@ abstract class AbstractInput(
126148
}
127149

128150
fun setPrimaryInputObserverId(id: Long) {
129-
val inputObservers = this.inputObserverIds.toMutableList()
151+
val inputObservers = this.inputObserverIds
152+
.toMutableList()
130153
.apply {
131154
add(
132155
0,
@@ -204,6 +227,11 @@ abstract class AbstractInput(
204227

205228
abstract fun getTaxaFromParcel(source: Parcel): List<AbstractInputTaxon>
206229

230+
enum class Status {
231+
DRAFT,
232+
TO_SYNC
233+
}
234+
207235
/**
208236
* Generates a pseudo unique ID. The value is the number of seconds since Jan. 1, 2016, midnight.
209237
*

0 commit comments

Comments
 (0)