diff --git a/.drone.yml b/.drone.yml
new file mode 100644
index 0000000..60548c8
--- /dev/null
+++ b/.drone.yml
@@ -0,0 +1,110 @@
+kind: pipeline
+type: docker
+name: default
+
+steps:
+- name: build
+ image: nextcloudci/android:android-49
+ volumes:
+ - name: gradle
+ path: /root/.gradle
+ - name: android
+ path: /opt/android-sdk-linux
+ commands:
+ - ./gradlew build
+
+- name: notify
+ image: appleboy/drone-telegram
+ settings:
+ token:
+ from_secret: telegram_token
+ to:
+ from_secret: telegram_user
+ format: markdown
+ message: |
+ Build [#{{build.number}}]({{build.link}}) from commit [{{truncate commit.sha 10}}]({{commit.link}}) on {{commit.branch}} **failed**.
+ {{uppercasefirst commit.author}} please fix me!
+ when:
+ status:
+ - failure
+
+volumes:
+- name: gradle
+ host:
+ path: /home/drone/.gradle
+- name: android
+ host:
+ path: /home/drone/Android/Sdk
+
+trigger:
+ event:
+ exclude:
+ - tag
+
+---
+kind: pipeline
+type: docker
+name: release
+
+steps:
+- name: build
+ image: nextcloudci/android:android-49
+ volumes:
+ - name: gradle
+ path: /root/.gradle
+ - name: android
+ path: /opt/android-sdk-linux
+ - name: tmp
+ path: /drone/src/out
+ commands:
+ - ./gradlew build
+ - ./gradlew :wrapper:fatJar
+ - mv /drone/src/wrapper/build/libs/*.jar /drone/src/out/
+ - mv /drone/src/app/build/outputs/apk/release/*.apk /drone/src/out/
+
+- name: gitea release
+ image: plugins/gitea-release
+ volumes:
+ - name: tmp
+ path: /drone/src/out
+ settings:
+ api_key:
+ from_secret: gitea_release
+ base_url: https://git.norangeb.it
+ title: wedroid
+ files:
+ - /drone/src/out/*.jar
+ - /drone/src/out/*.apk
+ checksum:
+ - md5
+ - sha256
+
+- name: notify
+ image: appleboy/drone-telegram
+ settings:
+ token:
+ from_secret: telegram_token
+ to:
+ from_secret: telegram_user
+ format: markdown
+ message: |
+ {{#success build.status}}
+ Release-{{build.tag}} successfully released.
+ {{else}}
+ Build [#{{build.number}}]({{build.link}}) from commit [{{truncate commit.sha 10}}]({{commit.link}}) with tag {{build.tag}} **failed**.
+ {{uppercasefirst commit.author}} please fix me!
+ {{/success}}
+
+volumes:
+- name: tmp
+ temp: {}
+- name: gradle
+ host:
+ path: /home/drone/.gradle
+- name: android
+ host:
+ path: /home/drone/Android/Sdk
+
+trigger:
+ event:
+ - tag
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aaba528
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,180 @@
+
+# Created by https://www.gitignore.io/api/java,linux,gradle,windows,android
+# Edit at https://www.gitignore.io/?templates=java,linux,gradle,windows,android
+
+### Android ###
+# Built application files
+*.apk
+*.ap_
+*.aab
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+release/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+.idea/uiDesigner.xml
+# Android Studio 3 in .gitignore file.
+.idea/caches
+.idea/modules.xml
+# Comment next line if keeping position of elements in Navigation Editor is relevant for you
+.idea/navEditor.xml
+
+# Keystore files
+# Uncomment the following lines if you do not want to check your keystore files in.
+#*.jks
+#*.keystore
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+
+# Google Services (e.g. APIs or Firebase)
+# google-services.json
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+# Version control
+vcs.xml
+
+# lint
+lint/intermediates/
+lint/generated/
+lint/outputs/
+lint/tmp/
+# lint/reports/
+
+### Android Patch ###
+gen-external-apklibs
+output.json
+
+# Replacement of .externalNativeBuild directories introduced
+# with Android Studio 3.5.
+.cxx/
+
+### Java ###
+# Compiled class file
+
+# Log file
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+### Gradle ###
+.gradle
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
+
+### Gradle Patch ###
+**/build/
+
+# End of https://www.gitignore.io/api/java,linux,gradle,windows,android
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..45b5654
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..6e6eec1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copyright/GPL_v__2_0.xml b/.idea/copyright/GPL_v__2_0.xml
new file mode 100644
index 0000000..6ac8137
--- /dev/null
+++ b/.idea/copyright/GPL_v__2_0.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..29bb4c5
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index eb76347..b8279d5 100644
--- a/README.md
+++ b/README.md
@@ -6,10 +6,8 @@
Client Android per Wekan sviluppato per il progetto del corso magistrale Ingegneria del Software per l'Università del Sannio.
+## Authors
-
-
-
-
-
-
+- Furno Umberto
+- Mignone Raffaele
+- Mincolelli Noemi
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..4180076
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,78 @@
+apply plugin: 'com.android.application'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+apply plugin: 'kotlin-kapt'
+
+apply plugin: 'io.gitlab.arturbosch.detekt'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.2"
+ defaultConfig {
+ applicationId "it.unisannio.ding.ids.wedroid.app"
+ minSdkVersion 24
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ packagingOptions {
+ exclude 'META-INF/atomicfu.kotlin_module'
+ }
+}
+
+dependencies {
+ // standard
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.core:core-ktx:1.1.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'com.google.android.material:material:1.0.0'
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ // wrapper
+ implementation project(':wrapper')
+ // retrofit
+ implementation "com.squareup.retrofit2:converter-gson:2.6.2"
+ implementation "com.squareup.retrofit2:retrofit:2.6.2"
+ // room database
+ kapt "androidx.room:room-compiler:$rootProject.roomVersion"
+ implementation "androidx.room:room-ktx:$rootProject.roomVersion"
+ implementation "androidx.room:room-runtime:$rootProject.roomVersion"
+ // lifecycle components
+ kapt "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion"
+ implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion"
+ // viewmode kotlin support
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.archLifecycleVersion"
+ // coroutines
+ api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutines"
+ // UI
+ implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
+ implementation 'androidx.cardview:cardview:1.0.0'
+ implementation "com.google.android.material:material:$rootProject.materialVersion"
+
+ // testing
+ testImplementation "com.squareup.okhttp3:mockwebserver:4.2.1"
+ testImplementation "io.mockk:mockk:1.9.3"
+ testImplementation 'junit:junit:4.12'
+
+ // android testing
+ androidTestImplementation "androidx.arch.core:core-testing:$rootProject.androidxArchVersion"
+ androidTestImplementation "androidx.arch.core:core-testing:$rootProject.coreTestingVersion"
+ androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.2.0'
+ androidTestImplementation 'androidx.test:runner:1.2.0'
+ androidTestImplementation 'androidx.test:rules:1.2.0'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+
+ // detekt
+ detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:1.2.2"
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/TestHelper.kt b/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/TestHelper.kt
new file mode 100644
index 0000000..017874f
--- /dev/null
+++ b/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/TestHelper.kt
@@ -0,0 +1,22 @@
+package it.unisannio.ding.ids.wedroid.app
+
+import androidx.lifecycle.*
+
+class OneTimeObserver(private val handler: (T) -> Unit) : Observer, LifecycleOwner {
+ private val lifecycle = LifecycleRegistry(this)
+ init {
+ lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ }
+
+ override fun getLifecycle(): Lifecycle = lifecycle
+
+ override fun onChanged(t: T) {
+ handler(t)
+ lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+ }
+}
+
+fun LiveData.observeOnce(onChangeHandler: (T) -> Unit) {
+ val observer = OneTimeObserver(handler = onChangeHandler)
+ observe(observer, observer)
+}
diff --git a/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/data/dao/BoardDaoTest.kt b/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/data/dao/BoardDaoTest.kt
new file mode 100644
index 0000000..e6151bb
--- /dev/null
+++ b/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/data/dao/BoardDaoTest.kt
@@ -0,0 +1,110 @@
+package it.unisannio.ding.ids.wedroid.app.data.dao
+
+import android.content.Context
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.room.Room
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import it.unisannio.ding.ids.wedroid.app.data.database.BoardDatabase
+import it.unisannio.ding.ids.wedroid.app.data.entity.Board
+import it.unisannio.ding.ids.wedroid.app.observeOnce
+import junit.framework.TestCase.assertEquals
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.IOException
+
+@RunWith(AndroidJUnit4::class)
+class BoardDaoTest {
+ private lateinit var dao: BoardDao
+ private lateinit var db: BoardDatabase
+
+ @get:Rule
+ val instantTaskExecutorRule = InstantTaskExecutorRule()
+
+ @Before
+ fun createDb() {
+ val context = ApplicationProvider.getApplicationContext()
+ db = Room.inMemoryDatabaseBuilder(
+ context, BoardDatabase::class.java
+ ).build()
+ dao = db.boardDao()
+ }
+
+ @After
+ @Throws(IOException::class)
+ fun closeDb() {
+ db.close()
+ }
+
+ @Test
+ fun emptyDatabaseOnCreation() {
+ dao.getAllBoard().observeOnce {
+ assertEquals(0, it.size)
+ }
+ }
+
+ @Test
+ fun insert() {
+ val board = Board("id", "title")
+
+ runBlocking {
+ dao.insert(board)
+ }
+
+ dao.getAllBoard().observeOnce {
+ assertEquals(1, it.size)
+ assertEquals(board, it[0])
+ }
+ }
+
+ @Test
+ fun replaceOnConflict() {
+ val board0 = Board("id", "title0")
+ val board1 = Board("id", "title1")
+
+ runBlocking {
+ dao.insert(board0)
+ dao.insert(board1)
+ }
+
+ dao.getAllBoard().observeOnce {
+ assertEquals(1, it.size)
+ assertEquals("title1", it[0].title)
+ }
+ }
+
+ @Test
+ fun getInAscendingOrder() {
+ val board0 = Board("id0", "title0")
+ val board1 = Board("id1", "title1")
+
+ runBlocking {
+ dao.insert(board1)
+ dao.insert(board0)
+ }
+
+ dao.getAllBoard().observeOnce {
+ assertEquals(2, it.size)
+ assertEquals(board0, it[0])
+ assertEquals(board1, it[1])
+ }
+ }
+
+ @Test
+ fun delete() {
+ val board = Board("id", "title")
+
+ runBlocking {
+ dao.insert(board)
+ dao.delete(board)
+ }
+
+ dao.getAllBoard().observeOnce {
+ assertEquals(0, it.size)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/data/dao/WListDaoTest.kt b/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/data/dao/WListDaoTest.kt
new file mode 100644
index 0000000..8e9096d
--- /dev/null
+++ b/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/data/dao/WListDaoTest.kt
@@ -0,0 +1,111 @@
+package it.unisannio.ding.ids.wedroid.app.data.dao
+
+import android.content.Context
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.room.Room
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import it.unisannio.ding.ids.wedroid.app.data.database.WListDatabase
+import it.unisannio.ding.ids.wedroid.app.data.entity.WList
+import it.unisannio.ding.ids.wedroid.app.observeOnce
+import junit.framework.TestCase.assertEquals
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.IOException
+
+@RunWith(AndroidJUnit4::class)
+class WListDaoTest {
+ private lateinit var dao: WListDao
+ private lateinit var db: WListDatabase
+
+ @get:Rule
+ val instantTaskExecutorRule = InstantTaskExecutorRule()
+
+ @Before
+ fun createDb() {
+ val context = ApplicationProvider.getApplicationContext()
+ db = Room.inMemoryDatabaseBuilder(
+ context, WListDatabase::class.java
+ ).build()
+ dao = db.wListDao()
+ }
+
+ @After
+ @Throws(IOException::class)
+ fun closeDb() {
+ db.close()
+ }
+
+ @Test
+ fun emptyDatabaseOnCreation() {
+ dao.allWList.observeOnce {
+ assertEquals(0, it.size)
+ }
+ }
+
+ @Test
+ fun insert() {
+ val wList = WList("id", "title")
+
+ runBlocking {
+ dao.insert(wList)
+ }
+
+ dao.allWList.observeOnce {
+ assertEquals(1, it.size)
+ assertEquals(wList, it[0])
+ }
+ }
+
+ @Test
+ fun replaceOnConflict() {
+ val wList0 = WList("id", "title0")
+ val wList1 = WList("id", "title1")
+
+ runBlocking {
+ dao.insert(wList0)
+ dao.insert(wList1)
+ }
+
+ dao.allWList.observeOnce {
+ assertEquals(1, it.size)
+ assertEquals("title1", it[0].title)
+ }
+ }
+
+ @Test
+ fun getInAscendingOrder() {
+ val wList0 = WList("id0", "title0")
+ val wList1 = WList("id1", "title1")
+
+ runBlocking {
+ dao.insert(wList1)
+ dao.insert(wList0)
+ }
+
+ dao.allWList.observeOnce {
+ assertEquals(2, it.size)
+ assertEquals(wList0, it[0])
+ assertEquals(wList1, it[1])
+ }
+ }
+
+ @Test
+ fun delete() {
+ val wlist = WList("id", "title")
+
+ runBlocking {
+ dao.insert(wlist)
+ dao.delete(wlist)
+ }
+
+ dao.allWList.observeOnce {
+ assertEquals(0, it.size)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/view/BoardsListsActivityTest.kt b/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/view/BoardsListsActivityTest.kt
new file mode 100644
index 0000000..6fe7e0a
--- /dev/null
+++ b/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/view/BoardsListsActivityTest.kt
@@ -0,0 +1,52 @@
+package it.unisannio.ding.ids.wedroid.app.view
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.swipeDown
+import androidx.test.espresso.action.ViewActions.swipeLeft
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.contrib.RecyclerViewActions
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.rule.ActivityTestRule
+import it.unisannio.ding.ids.wedroid.app.R
+import it.unisannio.ding.ids.wedroid.app.view.adapter.BoardsListAdapter
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class BoardsListsActivityTest {
+
+ @get:Rule
+ val activityRule = ActivityTestRule(BoardsListsActivity::class.java)
+
+ fun swipeLeftToDelete() {
+ onView(withId(R.id.boardList))
+ .perform(
+ RecyclerViewActions.actionOnItemAtPosition(
+ 0, swipeLeft()
+ )
+ )
+ }
+
+ fun pullToRefresh() {
+ onView(withId(R.id.pullToRefresh))
+ .perform(swipeDown())
+
+ }
+
+ @Test
+ fun openNewBoardActivity() {
+ onView(withId(R.id.fab))
+ .perform(click())
+
+ onView(withId(R.id.newBoardName))
+ .check(matches(isDisplayed()))
+ }
+
+}
+
diff --git a/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/view/LoginActivityTest.kt b/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/view/LoginActivityTest.kt
new file mode 100644
index 0000000..62af7b7
--- /dev/null
+++ b/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/view/LoginActivityTest.kt
@@ -0,0 +1,87 @@
+package it.unisannio.ding.ids.wedroid.app.view
+
+import androidx.test.espresso.Espresso.closeSoftKeyboard
+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.matcher.RootMatchers.withDecorView
+import androidx.test.espresso.matcher.ViewMatchers.*
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.rule.ActivityTestRule
+import it.unisannio.ding.ids.wedroid.app.R
+import org.hamcrest.CoreMatchers.not
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class LoginActivityTest {
+ @get:Rule
+ val activityRule = ActivityTestRule(LoginActivity::class.java)
+
+ @Test
+ fun loginWithEmptyUrl() {
+ onView(withId(R.id.username))
+ .perform(typeText("username"))
+ onView(withId(R.id.password))
+ .perform(typeText("password"))
+ closeSoftKeyboard()
+ onView(withId(R.id.button))
+ .perform(click())
+
+ onView(withText(R.string.login_empty_field))
+ .inRoot(withDecorView(not(activityRule.activity.window.decorView)))
+ .check(matches(isDisplayed()))
+ }
+
+ @Test
+ fun loginWithEmptyUsername() {
+ onView(withId(R.id.instanceServer))
+ .perform(typeText("https://wekan.com"))
+ onView(withId(R.id.password))
+ .perform(typeText("password"))
+ closeSoftKeyboard()
+ onView(withId(R.id.button))
+ .perform(click())
+
+ onView(withText(R.string.login_empty_field))
+ .inRoot(withDecorView(not(activityRule.activity.window.decorView)))
+ .check(matches(isDisplayed()))
+
+ }
+
+ @Test
+ fun loginWithEmptyPassword() {
+ onView(withId(R.id.instanceServer))
+ .perform(typeText("https://wekan.com"))
+ onView(withId(R.id.username))
+ .perform(typeText("username"))
+ closeSoftKeyboard()
+ onView(withId(R.id.button))
+ .perform(click())
+
+ onView(withText(R.string.login_empty_field))
+ .inRoot(withDecorView(not(activityRule.activity.window.decorView)))
+ .check(matches(isDisplayed()))
+ }
+
+ @Test
+ fun loginWithUnformedInstance(){
+ onView(withId(R.id.instanceServer))
+ .perform(typeText("not an URL"))
+ onView(withId(R.id.username))
+ .perform(typeText("username"))
+ onView(withId(R.id.password))
+ .perform(typeText("password"))
+ closeSoftKeyboard()
+ onView(withId(R.id.button))
+ .perform(click())
+
+ onView(withText(R.string.login_unformed_instance))
+ .inRoot(withDecorView(not(activityRule.activity.window.decorView)))
+ .check(matches(isDisplayed()))
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/view/NewBoardActivityTest.kt b/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/view/NewBoardActivityTest.kt
new file mode 100644
index 0000000..4f49761
--- /dev/null
+++ b/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/view/NewBoardActivityTest.kt
@@ -0,0 +1,37 @@
+package it.unisannio.ding.ids.wedroid.app.view
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.RootMatchers.withDecorView
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.rule.ActivityTestRule
+import it.unisannio.ding.ids.wedroid.app.R
+import org.hamcrest.core.IsNot.not
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class NewBoardActivityTest {
+
+ @get:Rule
+ val activityRule = ActivityTestRule(NewBoardActivity::class.java)
+
+ @Test
+ fun showToastOnEmptyName() {
+ onView(withId(R.id.newBoardDone))
+ .perform(click())
+
+ onView(withText(R.string.on_add_new_board_empty_name))
+ .inRoot(withDecorView(not(activityRule.activity.window.decorView)))
+ .check(matches(isDisplayed()))
+ }
+
+}
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d6d9155
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/dao/BoardDao.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/dao/BoardDao.kt
new file mode 100644
index 0000000..adcfa85
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/dao/BoardDao.kt
@@ -0,0 +1,21 @@
+package it.unisannio.ding.ids.wedroid.app.data.dao
+
+import androidx.lifecycle.LiveData
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import it.unisannio.ding.ids.wedroid.app.data.entity.Board
+
+@Dao
+interface BoardDao {
+ @Query("SELECT * from board_table ORDER BY title ASC")
+ fun getAllBoard(): LiveData>
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insert(board: Board)
+
+ @Delete
+ suspend fun delete(board: Board)
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/dao/WListDao.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/dao/WListDao.java
new file mode 100644
index 0000000..3c74fe7
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/dao/WListDao.java
@@ -0,0 +1,27 @@
+package it.unisannio.ding.ids.wedroid.app.data.dao;
+
+import androidx.lifecycle.LiveData;
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+
+import java.util.List;
+
+//import io.reactivex.Completable;
+import it.unisannio.ding.ids.wedroid.app.data.entity.WList;
+
+@Dao
+public interface WListDao {
+
+ @Query("SELECT * from wlist_table ORDER BY title ASC")
+ LiveData> getAllWList();
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ void insert(WList wList);
+
+ @Delete
+ void delete(WList wList);
+
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/database/BoardDatabase.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/database/BoardDatabase.java
new file mode 100644
index 0000000..b9c6021
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/database/BoardDatabase.java
@@ -0,0 +1,31 @@
+package it.unisannio.ding.ids.wedroid.app.data.database;
+
+import android.content.Context;
+
+import androidx.room.Database;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
+import it.unisannio.ding.ids.wedroid.app.data.dao.BoardDao;
+import it.unisannio.ding.ids.wedroid.app.data.entity.Board;
+
+@Database(entities = Board.class, version = 1, exportSchema = false)
+public abstract class BoardDatabase extends RoomDatabase {
+ private static volatile BoardDatabase INSTANCE;
+
+ public abstract BoardDao boardDao();
+
+ public static BoardDatabase getDatabase(Context context) {
+ if (INSTANCE != null)
+ return INSTANCE;
+ synchronized (BoardDatabase.class) {
+ INSTANCE = Room.databaseBuilder(
+ context.getApplicationContext(),
+ BoardDatabase.class,
+ "board_database"
+ ).build();
+
+ return INSTANCE;
+ }
+ }
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/database/WListDatabase.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/database/WListDatabase.java
new file mode 100644
index 0000000..f35da1e
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/database/WListDatabase.java
@@ -0,0 +1,31 @@
+package it.unisannio.ding.ids.wedroid.app.data.database;
+
+import android.content.Context;
+
+import androidx.room.Database;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
+import it.unisannio.ding.ids.wedroid.app.data.dao.WListDao;
+import it.unisannio.ding.ids.wedroid.app.data.entity.WList;
+
+@Database(entities = WList.class, version = 2, exportSchema = false)
+public abstract class WListDatabase extends RoomDatabase {
+ private static volatile WListDatabase INSTANCE;
+
+ public abstract WListDao wListDao();
+
+ public static WListDatabase getDatabase(Context context) {
+ if (INSTANCE != null)
+ return INSTANCE;
+ synchronized (WListDatabase.class) {
+ INSTANCE = Room.databaseBuilder(
+ context.getApplicationContext(),
+ WListDatabase.class,
+ "wlist_database"
+ ).fallbackToDestructiveMigration().build();
+
+ return INSTANCE;
+ }
+ }
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/entity/Board.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/entity/Board.kt
new file mode 100644
index 0000000..eb61071
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/entity/Board.kt
@@ -0,0 +1,15 @@
+package it.unisannio.ding.ids.wedroid.app.data.entity
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "board_table")
+data class Board(
+ @PrimaryKey @ColumnInfo(name = "id") val id: String,
+ @ColumnInfo(name = "title") val title: String = ""
+)
+
+fun it.unisannio.ding.ids.wedroid.wrapper.entity.Board.convert(): Board {
+ return Board(this.id, this.title)
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/entity/WList.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/entity/WList.kt
new file mode 100644
index 0000000..1fedd70
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/entity/WList.kt
@@ -0,0 +1,15 @@
+package it.unisannio.ding.ids.wedroid.app.data.entity
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "wlist_table")
+data class WList(
+ @PrimaryKey @ColumnInfo(name = "id") val id: String,
+ @ColumnInfo(name = "title") val title: String = ""
+)
+
+fun it.unisannio.ding.ids.wedroid.wrapper.entity.WList.convert(): WList {
+ return WList(this.id, this.title)
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepository.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepository.kt
new file mode 100644
index 0000000..0c9e4de
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepository.kt
@@ -0,0 +1,161 @@
+package it.unisannio.ding.ids.wedroid.app.data.repository
+
+import android.util.Log
+import it.unisannio.ding.ids.wedroid.app.data.dao.BoardDao
+import it.unisannio.ding.ids.wedroid.app.data.entity.Board
+import it.unisannio.ding.ids.wedroid.app.data.entity.convert
+import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader
+import it.unisannio.ding.ids.wedroid.wrapper.api.BoardService
+import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardBackgroundColor
+import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardPermission
+import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardPrototype
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+
+class BoardRepository(
+ private val dao: BoardDao,
+ private val service: BoardService,
+ private val reader: PreferenceReader
+) {
+ val allBoards by lazy {
+ dao.getAllBoard()
+ }
+
+ init {
+ synchronize()
+ }
+
+ fun synchronize() {
+ service.getBoardsFromUser(reader.userId)
+ .enqueue(object :
+ Callback> {
+ override fun onFailure(
+ call: Call>,
+ t: Throwable
+ ) = logNetworkError(t.message)
+
+ override fun onResponse(
+ call: Call>,
+ response: Response>
+ ) {
+ CoroutineScope(Dispatchers.IO).launch {
+ synchronizeCallback(response)
+ }
+ }
+ })
+ }
+
+ private suspend fun synchronizeCallback(
+ response: Response>
+ ) {
+ if (!response.isSuccessful) {
+ logNetworkError("${response.code()} ${response.message()}")
+ return
+ }
+
+ // read boards from the body
+ val boards = (response.body() ?: return)
+ .map { it.convert() }
+
+ addNewBoardToDb(boards)
+
+ removeOldBoardsFromDb(boards)
+ }
+
+ fun insertBoard(title: String, isPrivate: Boolean, color: BoardBackgroundColor) {
+ val permission = if (isPrivate) BoardPermission.PRIVATE else BoardPermission.PUBLIC
+
+ service.newBoard(
+ BoardPrototype.Builder()
+ .setOwner(reader.userId)
+ .setTitle(title)
+ .setBackgroundColor(color)
+ .setBoardPermission(permission)
+ .build()
+ ).enqueue(object : Callback {
+ override fun onFailure(
+ call: Call,
+ t: Throwable
+ ) = logNetworkError(t.message)
+
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ CoroutineScope(Dispatchers.IO).launch {
+ insertBoardCallback(response, title)
+ }
+ }
+ })
+ }
+
+ private suspend fun insertBoardCallback(
+ response: Response,
+ title: String
+ ) {
+ if (!response.isSuccessful) {
+ logNetworkError("${response.code()} ${response.message()}")
+ return
+ }
+
+ val board = response.body()
+
+ if (board == null) {
+ logNetworkError("empty body")
+ return
+ }
+
+ dao.insert(Board(board.id, title))
+ }
+
+ fun deleteBoard(id: String) {
+ service.deleteBoard(id).enqueue(
+ object : Callback {
+ override fun onFailure(call: Call, t: Throwable) {
+ logNetworkError(t.message)
+ }
+
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ CoroutineScope(Dispatchers.IO).launch {
+ deleteBoardCallback(response, id)
+ }
+ }
+ })
+ }
+
+ private suspend fun deleteBoardCallback(
+ response: Response,
+ id: String
+ ) {
+ if (!response.isSuccessful) {
+ logNetworkError("${response.code()} ${response.message()}")
+ return
+ }
+
+ dao.delete(Board(id))
+ }
+
+ private suspend fun addNewBoardToDb(boards: Collection) {
+ boards.forEach {
+ dao.insert(it)
+ }
+ }
+
+ private suspend fun removeOldBoardsFromDb(boards: Collection) {
+ allBoards.value?.minus(boards)
+ ?.forEach {
+ dao.delete(it)
+ }
+ }
+
+ private fun logNetworkError(message: String?) {
+ Log.e("RETROFIT", message)
+ }
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/WListRepository.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/WListRepository.kt
new file mode 100644
index 0000000..c377bbc
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/WListRepository.kt
@@ -0,0 +1,116 @@
+package it.unisannio.ding.ids.wedroid.app.data.repository
+
+import android.util.Log
+
+import androidx.lifecycle.LiveData
+import it.unisannio.ding.ids.wedroid.app.data.dao.WListDao
+import it.unisannio.ding.ids.wedroid.app.data.entity.WList
+import it.unisannio.ding.ids.wedroid.app.data.entity.convert
+import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader
+import it.unisannio.ding.ids.wedroid.wrapper.api.ListService
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+
+class WListRepository(
+ private val dao: WListDao,
+ private val service: ListService,
+ private val reader: PreferenceReader
+) {
+
+ val allWLists: LiveData> by lazy {
+ dao.allWList
+ }
+
+ init {
+ synchronize()
+ }
+
+ fun synchronize() {
+ service.getAllList(reader.boardId)
+ .enqueue(object :
+ Callback> {
+ override fun onFailure(
+ call: Call>,
+ t: Throwable
+ ) = logNetworkError(t.message)
+
+ override fun onResponse(
+ call: Call>,
+ response: Response>
+ ) {
+ CoroutineScope(Dispatchers.IO).launch {
+ synchronizeCallback(response)
+ }
+ }
+ })
+ }
+
+ private fun synchronizeCallback(
+ response: Response>
+ ) {
+ if (!response.isSuccessful) {
+ logNetworkError("${response.code()} ${response.message()}")
+ return
+ }
+
+ val wLists = (response.body() ?: return)
+ .map { it.convert() }
+
+ addNewWListToDb(wLists)
+ removeOldWListsFromDb(wLists)
+ }
+
+ fun deleteWList(idBoard: String, idWList: String) {
+ service.deleteList(idBoard, idWList).enqueue(
+ object : Callback {
+ override fun onFailure(
+ call: Call,
+ t: Throwable
+ ) {
+ logNetworkError(t.message)
+ }
+
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ CoroutineScope(Dispatchers.IO).launch {
+ deleteWListCallBack(response, idWList)
+ }
+ }
+ })
+ }
+
+ private fun deleteWListCallBack(
+ response: Response,
+ id: String
+ ) {
+ if (!response.isSuccessful) {
+ logNetworkError("${response.code()} ${response.message()}")
+ return
+ }
+ dao.delete(WList(id))
+ }
+
+ private fun addNewWListToDb(wLists: Collection) {
+ wLists.forEach {
+ dao.insert(it)
+ }
+ }
+
+ private fun removeOldWListsFromDb(wLists: Collection) {
+ allWLists.value?.minus(wLists)
+ ?.forEach {
+ dao.delete(it)
+ }
+ }
+
+ @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
+ private fun logNetworkError(message: String?) {
+ Log.e("RETROFIT", message)
+ }
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/PreferenceReader.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/PreferenceReader.java
new file mode 100644
index 0000000..2574e01
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/PreferenceReader.java
@@ -0,0 +1,11 @@
+package it.unisannio.ding.ids.wedroid.app.util;
+
+public interface PreferenceReader {
+ String getBaseUrl();
+
+ String getUserId();
+
+ String getToken();
+
+ String getBoardId();
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/PreferenceWriter.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/PreferenceWriter.java
new file mode 100644
index 0000000..74685c8
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/PreferenceWriter.java
@@ -0,0 +1,11 @@
+package it.unisannio.ding.ids.wedroid.app.util;
+
+public interface PreferenceWriter {
+ void setBaseUrl(String baseUrl);
+
+ void setUserId(String userId);
+
+ void setToken(String token);
+
+ void setBoardId(String boardId);
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/ServicesFactory.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/ServicesFactory.kt
new file mode 100644
index 0000000..c3c3a83
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/ServicesFactory.kt
@@ -0,0 +1,83 @@
+package it.unisannio.ding.ids.wedroid.app.util
+
+import it.unisannio.ding.ids.wedroid.wrapper.api.BoardService
+import it.unisannio.ding.ids.wedroid.wrapper.api.CardCommentService
+import it.unisannio.ding.ids.wedroid.wrapper.api.CardService
+import it.unisannio.ding.ids.wedroid.wrapper.api.ChecklistService
+import it.unisannio.ding.ids.wedroid.wrapper.api.UserService
+import it.unisannio.ding.ids.wedroid.wrapper.api.ListService
+import it.unisannio.ding.ids.wedroid.wrapper.api.SwimlanesService
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+class ServicesFactory(
+ reader: PreferenceReader
+) {
+ private val retrofit: Retrofit
+
+ init {
+ val httpClient = OkHttpClient.Builder()
+ .addInterceptor {
+ val request = it.request().newBuilder()
+ .addHeader("Authorization", "Bearer ${reader.token}")
+ .addHeader("Accept", "application/json")
+ .addHeader("Content-type", "application/json")
+ .build()
+
+ it.proceed(request)
+ }.build()
+
+ retrofit = Retrofit.Builder()
+ .baseUrl(reader.baseUrl)
+ .addConverterFactory(GsonConverterFactory.create())
+ .client(httpClient)
+ .build()
+ }
+
+ val boardService by lazy {
+ retrofit.create(BoardService::class.java)
+ }
+
+ val cardCommentService by lazy {
+ retrofit.create(CardCommentService::class.java)
+ }
+
+ val cardService by lazy {
+ retrofit.create(CardService::class.java)
+ }
+
+ val checklistService by lazy {
+ retrofit.create(ChecklistService::class.java)
+ }
+
+ val listService by lazy {
+ retrofit.create(ListService::class.java)
+ }
+
+ val swimlanesService by lazy {
+ retrofit.create(SwimlanesService::class.java)
+ }
+
+ val userService by lazy {
+ retrofit.create(UserService::class.java)
+ }
+
+ companion object {
+ @Volatile
+ private var INSTANCE: ServicesFactory? = null
+
+ fun getInstance(reader: PreferenceReader): ServicesFactory {
+ val tempInstance = INSTANCE
+
+ if (tempInstance != null)
+ return tempInstance
+
+ synchronized(this) {
+ val instance = ServicesFactory(reader)
+ INSTANCE = instance
+ return instance
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/SharedPreferenceHelper.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/SharedPreferenceHelper.kt
new file mode 100644
index 0000000..e69ca56
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/SharedPreferenceHelper.kt
@@ -0,0 +1,43 @@
+package it.unisannio.ding.ids.wedroid.app.util
+
+import android.content.Context
+
+class SharedPreferenceHelper(context: Context) : PreferenceReader, PreferenceWriter {
+ private val sp = context.getSharedPreferences("userinfo", Context.MODE_PRIVATE)
+
+ override fun getBaseUrl(): String? {
+ return sp.getString("url", "")
+ }
+
+ override fun getUserId(): String? {
+ return sp.getString("id", "")
+ }
+
+ override fun getToken(): String? {
+ return sp.getString("token", "")
+ }
+
+ override fun setBaseUrl(baseUrl: String?) {
+ val editor = sp.edit()
+ editor.putString("url", baseUrl).apply()
+ }
+
+ override fun setUserId(userId: String?) {
+ val editor = sp.edit()
+ editor.putString("id", userId).apply()
+ }
+
+ override fun setToken(token: String?) {
+ val editor = sp.edit()
+ editor.putString("token", token).apply()
+ }
+
+ override fun getBoardId(): String? {
+ return sp.getString("boardId", "")
+ }
+
+ override fun setBoardId(token: String?) {
+ val editor = sp.edit()
+ editor.putString("boardId", token).apply()
+ }
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/BoardViewActivity.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/BoardViewActivity.java
new file mode 100644
index 0000000..2b147a9
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/BoardViewActivity.java
@@ -0,0 +1,231 @@
+package it.unisannio.ding.ids.wedroid.app.view;
+
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+import it.unisannio.ding.ids.wedroid.app.R;
+import it.unisannio.ding.ids.wedroid.app.util.ServicesFactory;
+import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper;
+import it.unisannio.ding.ids.wedroid.wrapper.entity.Board;
+import it.unisannio.ding.ids.wedroid.wrapper.entity.User;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+public class BoardViewActivity extends AppCompatActivity {
+
+ static final int WLISTS_REQUEST = 30;
+
+ private String idBoard, username, boardTitle;
+ private int boardColor;
+ private TextView description, members, permission, creationDate, lastModificationDate;
+ private View divider1, divider2, divider3;
+ private ListView listView;
+ private Button getListsButton;
+ private SharedPreferenceHelper sp;
+ private ServicesFactory service;
+ private Toolbar myToolbar;
+ private Board board;
+ private SwipeRefreshLayout swipeRefreshLayout;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_board_view);
+
+ findViews();
+
+ setSupportActionBar(myToolbar);
+
+ swipeRefreshLayout.setRefreshing(false);
+
+ Intent i = getIntent();
+ idBoard = i.getStringExtra("idBoard");
+
+ sp = new SharedPreferenceHelper(this);
+ sp.setBoardId(idBoard);
+
+ initializeUI(idBoard);
+
+ getListsButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent i = new Intent(getApplicationContext(), WListsListActivity.class);
+ i.putExtra("idBoard", idBoard);
+ i.putExtra("barTitle", boardTitle);
+ i.putExtra("barColor", boardColor);
+ startActivityForResult(i, WLISTS_REQUEST);
+ }
+ });
+
+ swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ initializeUI(idBoard);
+ swipeRefreshLayout.setRefreshing(false);
+ }
+ });
+ }
+
+ private void findViews() {
+ myToolbar = findViewById(R.id.my_toolbar);
+ getListsButton = findViewById(R.id.getLists);
+ description = findViewById(R.id.descriptionTxt);
+ members = findViewById(R.id.membersTxt);
+ permission = findViewById(R.id.permissionTxt);
+ creationDate = findViewById(R.id.createdDate);
+ lastModificationDate = findViewById(R.id.modifiedDate);
+ divider1 = findViewById(R.id.divider1);
+ divider2 = findViewById(R.id.divider2);
+ divider3 = findViewById(R.id.divider3);
+ listView = findViewById(R.id.listViewID);
+ swipeRefreshLayout = findViewById(R.id.pullToRefresh);
+ }
+
+ private void initializeUI(String idBoard) {
+ service = new ServicesFactory(sp);
+ service.getBoardService().getBoard(idBoard).enqueue(new Callback() {
+ @Override
+ public void onResponse(@NotNull Call call, @NotNull Response response) {
+ assert response.body() != null;
+ board = response.body();
+ if (board.getId() == null) {
+ Toast.makeText(
+ getApplicationContext(),
+ getApplicationContext().getString(R.string.board_deleted),
+ Toast.LENGTH_LONG)
+ .show();
+ getListsButton.setEnabled(false);
+ } else {
+ Objects.requireNonNull(getSupportActionBar()).setTitle(board.getTitle());
+ myToolbar.setBackgroundColor(Color.parseColor(encodeColor(
+ board.getBackgroundColor().toString())));
+
+ boardTitle = board.getTitle();
+
+ boardColor = Color.parseColor(encodeColor(
+ board.getBackgroundColor().toString()));
+
+ Drawable background = getListsButton.getBackground();
+ background.setTint(boardColor);
+ getListsButton.setBackgroundDrawable(background);
+
+ description.setText(board.getDescription());
+ permission.setText(board.getPermission().toString());
+ members.setText("");
+ for (int i = 0; i < board.getMembers().size(); i++) {
+ replaceIDUserToUsername(board.getMembers().get(i).getUserId());
+ }
+
+ creationDate.setText(R.string.created_at);
+ creationDate.append("\n"
+ + board.getCreatedAt());
+ lastModificationDate.setText(R.string.modified_at);
+ lastModificationDate.append("\n"
+ + board.getModifiedAt());
+
+ divider1.setBackgroundColor(boardColor);
+ divider2.setBackgroundColor(boardColor);
+ divider3.setBackgroundColor(boardColor);
+
+ ArrayList labelsTitle = new ArrayList<>();
+ for (int i = 0; i < board.getLabels().size(); i++) {
+ labelsTitle.add(board.getLabels().get(i).getName());
+ }
+ ArrayAdapter adapter = new ArrayAdapter<>(getApplicationContext(),
+ android.R.layout.simple_list_item_1, labelsTitle);
+ listView.setAdapter(adapter);
+ getListsButton.setEnabled(true);
+ }
+ }
+
+ @Override
+ public void onFailure(@NotNull Call call, @NotNull Throwable t) {
+ Toast.makeText(getApplicationContext(),
+ "connection error",
+ Toast.LENGTH_LONG).show();
+ getListsButton.setEnabled(false);
+ }
+ });
+ }
+
+ private String encodeColor(String color) {
+ String encodedColor;
+ if (color.equalsIgnoreCase("belize")) {
+ encodedColor = "#2980B9";
+ } else if (color.equalsIgnoreCase("nephritis")) {
+ encodedColor = "#27AE60";
+ } else if (color.equalsIgnoreCase("pomegranate")) {
+ encodedColor = "#C0392B";
+ } else if (color.equalsIgnoreCase("pumpkin")) {
+ encodedColor = "#E67E22";
+ } else if (color.equalsIgnoreCase("wisteria")) {
+ encodedColor = "#8E44AD";
+ } else if (color.equalsIgnoreCase("moderatepink")) {
+ encodedColor = "#CD5A91";
+ } else if (color.equalsIgnoreCase("strongcyan")) {
+ encodedColor = "#00AECC";
+ } else if (color.equalsIgnoreCase("dark")) {
+ encodedColor = "#2C3E51";
+ } else if (color.equalsIgnoreCase("midnight")) {
+ encodedColor = "#2C3E50";
+ } else if (color.equalsIgnoreCase("relax")) {
+ encodedColor = "#27AE61";
+ } else if (color.equalsIgnoreCase("corteza")) {
+ encodedColor = "#568BA2";
+ } else
+ encodedColor = "#38DF87";
+
+ return encodedColor;
+ }
+
+ private void replaceIDUserToUsername(String idUser) {
+ service = new ServicesFactory(sp);
+ service.getUserService().getUser(idUser).enqueue(new Callback() {
+ @Override
+ public void onResponse(@NotNull Call call, @NotNull Response response) {
+ User u = response.body();
+ assert u != null;
+ username = u.getUsername();
+ if (u.isAdmin()) {
+ members.append("Admin: " + username + ";\n");
+ } else
+ members.append("Other member: " + username + ";\n");
+ }
+
+ @Override
+ public void onFailure(@NotNull Call call, @NotNull Throwable t) {
+ //TODO
+ }
+ });
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == WLISTS_REQUEST) {
+ if (resultCode != WListsListActivity.RESULT_OK) {
+ Toast.makeText(this, "It's not possible view the lists",
+ Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/BoardsListsActivity.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/BoardsListsActivity.kt
new file mode 100644
index 0000000..d3c6924
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/BoardsListsActivity.kt
@@ -0,0 +1,133 @@
+package it.unisannio.ding.ids.wedroid.app.view
+
+import android.content.Intent
+import android.os.Bundle
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import it.unisannio.ding.ids.wedroid.app.R
+import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader
+import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper
+import it.unisannio.ding.ids.wedroid.app.view.adapter.BoardsListAdapter
+import it.unisannio.ding.ids.wedroid.app.viewmodel.BoardsListViewModel
+import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardBackgroundColor
+
+import kotlinx.android.synthetic.main.activity_boards_lists.*
+import kotlinx.android.synthetic.main.content_boards_lists.*
+import java.util.Locale
+
+class BoardsListsActivity : AppCompatActivity() {
+ private lateinit var viewModel: BoardsListViewModel
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_boards_lists)
+ setSupportActionBar(toolbar)
+ val reader: PreferenceReader = SharedPreferenceHelper(this)
+
+ if (reader.token == "")
+ startActivityForResult(
+ Intent(this, LoginActivity::class.java),
+ LOGIN_CODE
+ )
+ else initializeUi()
+ }
+
+ private fun initializeUi() {
+ viewModel = ViewModelProvider(this).get(BoardsListViewModel::class.java)
+
+ val adapter = BoardsListAdapter(this)
+ boardList.adapter = adapter
+ boardList.layoutManager = LinearLayoutManager(this)
+
+ viewModel.allBoards.observe(this, Observer {
+ it.let { adapter.setBoards(it) }
+ pullToRefresh.isRefreshing = false
+ })
+
+ swipeLeftToDelete()
+
+ fab.setOnClickListener {
+ startActivityForResult(
+ Intent(this, NewBoardActivity::class.java),
+ NEW_BOARD_CODE
+ )
+ }
+
+ pullToRefresh.setColorSchemeColors(getColor(R.color.colorAccent))
+ pullToRefresh.setOnRefreshListener {
+ viewModel.refresh()
+ }
+ }
+
+ private fun swipeLeftToDelete() {
+ val swipeToDelete = ItemTouchHelper(
+ object : ItemTouchHelper.SimpleCallback(
+ 0, ItemTouchHelper.LEFT
+ ) {
+ override fun onMove(
+ recyclerView: RecyclerView,
+ viewHolder: RecyclerView.ViewHolder,
+ target: RecyclerView.ViewHolder
+ ): Boolean {
+ return false
+ }
+
+ override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
+ val pos = viewHolder.adapterPosition
+ viewModel.deleteBoard(pos)
+ }
+ })
+
+ swipeToDelete.attachToRecyclerView(boardList)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ when (requestCode) {
+ LOGIN_CODE -> onLoginResult(resultCode)
+ NEW_BOARD_CODE -> if (data != null) onAddBoardResult(resultCode, data)
+ else -> finish()
+ }
+ }
+
+ private fun onLoginResult(resultCode: Int) {
+ when (resultCode) {
+ LoginActivity.LOGIN_OK -> initializeUi()
+ LoginActivity.LOGIN_ERROR -> finish()
+ else -> finish()
+ }
+ }
+
+ private fun onAddBoardResult(resultCode: Int, data: Intent) {
+ if (resultCode != NewBoardActivity.RESULT_OK) {
+ Toast.makeText(this, R.string.on_add_new_board_error, Toast.LENGTH_LONG)
+ .show()
+ return
+ }
+
+ val title = data.getStringExtra(NewBoardActivity.BOARD_NAME)
+ if (title == null) {
+ Toast.makeText(this, R.string.on_null_new_board_name, Toast.LENGTH_LONG)
+ .show()
+ return
+ }
+
+ val isPrivate = data.getBooleanExtra(NewBoardActivity.BOARD_PRIVATE, true)
+
+ val colorName = data.getStringExtra(NewBoardActivity.BOARD_BACKGROUND_COLOR) ?: "belize"
+ val backgroundColor = BoardBackgroundColor
+ .valueOf(colorName.toUpperCase(Locale.ROOT))
+
+ viewModel.insertBoard(title, isPrivate, backgroundColor)
+ }
+
+ companion object {
+ const val NEW_BOARD_CODE = 17
+ const val LOGIN_CODE = 19
+ }
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/LoginActivity.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/LoginActivity.kt
new file mode 100644
index 0000000..d7405ff
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/LoginActivity.kt
@@ -0,0 +1,98 @@
+package it.unisannio.ding.ids.wedroid.app.view
+
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import android.view.View
+import android.webkit.URLUtil
+import android.widget.Toast
+import it.unisannio.ding.ids.wedroid.app.R
+import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper
+import it.unisannio.ding.ids.wedroid.wrapper.api.LoginService
+import it.unisannio.ding.ids.wedroid.wrapper.entity.UserPrototype
+import kotlinx.android.synthetic.main.activity_login.*
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+class LoginActivity : AppCompatActivity() {
+ lateinit var sph: SharedPreferenceHelper
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_login)
+ sph = SharedPreferenceHelper(this)
+
+ setResult(LOGIN_ERROR)
+
+ val id = sph.userId
+ val token = sph.token
+ val url = sph.baseUrl
+ }
+
+ fun loginButton(v: View) {
+ if (username.text.isBlank() || instanceServer.text.isBlank() || password.text.isBlank()) {
+ Toast.makeText(this, R.string.login_empty_field, Toast.LENGTH_LONG)
+ .show()
+ return
+ }
+
+ val userNameText = username.text.toString()
+ val passwordText = password.text.toString()
+ val instanceServerText = instanceServer.text.toString()
+
+ if (!URLUtil.isValidUrl(instanceServerText)) {
+ Toast.makeText(this, R.string.login_unformed_instance, Toast.LENGTH_LONG)
+ .show()
+ return
+ }
+
+ val service = Retrofit.Builder()
+ .addConverterFactory(GsonConverterFactory.create())
+ .baseUrl(instanceServerText)
+ .build()
+ .create(LoginService::class.java)
+
+ service.login(userNameText, passwordText).enqueue(object : Callback {
+ override fun onFailure(call: Call, t: Throwable) {
+ Toast.makeText(
+ applicationContext,
+ R.string.login_network_error,
+ Toast.LENGTH_LONG
+ ).show()
+ }
+
+ override fun onResponse(call: Call, response: Response) {
+
+ if (response.code() != HTTP_CODE_OK) {
+ Toast.makeText(
+ applicationContext,
+ R.string.login_wrong_field,
+ Toast.LENGTH_LONG
+ ).show()
+ return
+ }
+ val users = response.body()
+ sph.baseUrl = instanceServer.text.toString()
+ sph.token = users?.token
+ sph.userId = users?.id
+
+ Toast.makeText(
+ applicationContext,
+ R.string.login_success,
+ Toast.LENGTH_LONG
+ ).show()
+
+ setResult(LOGIN_OK)
+ finish()
+ }
+ })
+ }
+
+ companion object {
+ const val LOGIN_OK = 0
+ const val LOGIN_ERROR = 1
+ const val HTTP_CODE_OK = 200
+ }
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/NewBoardActivity.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/NewBoardActivity.java
new file mode 100644
index 0000000..887b245
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/NewBoardActivity.java
@@ -0,0 +1,69 @@
+package it.unisannio.ding.ids.wedroid.app.view;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
+import android.util.Log;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.Switch;
+import android.widget.Toast;
+
+import it.unisannio.ding.ids.wedroid.app.R;
+import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardBackgroundColor;
+
+public class NewBoardActivity extends AppCompatActivity {
+ private EditText boardName;
+ private Switch isPrivate;
+ private Spinner colorPicker;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_new_board);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ boardName = findViewById(R.id.newBoardName);
+ isPrivate = findViewById(R.id.newBoardPermission);
+
+ colorPicker = findViewById(R.id.newBoardColor);
+ ArrayAdapter adapter = ArrayAdapter.createFromResource(
+ this,
+ R.array.board_background_colors,
+ android.R.layout.simple_spinner_item
+ );
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ colorPicker.setAdapter(adapter);
+
+ }
+
+ public void onDone(View v) {
+ if (boardName.getText().toString().equals("")) {
+ Toast.makeText(this, R.string.on_add_new_board_empty_name, Toast.LENGTH_LONG)
+ .show();
+ return;
+ }
+
+ Intent data = new Intent();
+ data.putExtra(BOARD_NAME, boardName.getText().toString());
+ data.putExtra(BOARD_PRIVATE, isPrivate.isChecked());
+ data.putExtra(BOARD_BACKGROUND_COLOR, colorPicker.getSelectedItem().toString());
+ setResult(RESULT_OK, data);
+ finish();
+ }
+
+ public void onCancel(View v) {
+ finish();
+ }
+
+ public static final int RESULT_OK = 17;
+ public static final String BOARD_NAME = "BOARD_NAME";
+ public static final String BOARD_PRIVATE = "BOARD_PRIVATE";
+ public static final String BOARD_BACKGROUND_COLOR = "BOARD_BACKGROUND_COLOR";
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/WListsListActivity.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/WListsListActivity.java
new file mode 100644
index 0000000..e42b9f9
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/WListsListActivity.java
@@ -0,0 +1,118 @@
+package it.unisannio.ding.ids.wedroid.app.view;
+
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import java.util.List;
+import java.util.Objects;
+
+import it.unisannio.ding.ids.wedroid.app.R;
+
+import it.unisannio.ding.ids.wedroid.app.data.entity.WList;
+import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper;
+import it.unisannio.ding.ids.wedroid.app.view.adapter.WListsAdapter;
+import it.unisannio.ding.ids.wedroid.app.viewmodel.WListsListViewModel;
+
+public class WListsListActivity extends AppCompatActivity {
+
+ Toolbar myToolbar;
+ int barColor;
+ WListsListViewModel viewModel;
+ RecyclerView recyclerView;
+ //SwipeRefreshLayout swipeRefreshLayout;
+ SharedPreferenceHelper sp;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_wlists_view);
+ myToolbar = findViewById(R.id.my_toolbar);
+ setSupportActionBar(myToolbar);
+ //swipeRefreshLayout = findViewById(R.id.pullToRefresh);
+ sp = new SharedPreferenceHelper(this);
+
+ Intent i = getIntent();
+ String boardTitle = i.getStringExtra("barTitle");
+ Objects.requireNonNull(getSupportActionBar()).setTitle(boardTitle);
+ barColor = i.getIntExtra("barColor", 0);
+ setResult(WListsListActivity.RESULT_OK, i);
+
+ myToolbar.setBackgroundColor(barColor);
+
+ recyclerView = findViewById(R.id.recyclerviewWList);
+ initializeUi(sp.getBoardId());
+
+ FloatingActionButton fab = findViewById(R.id.synchronize);
+ fab.setBackgroundTintList(ColorStateList.valueOf(barColor));
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ viewModel.refresh();
+ }
+ });
+ }
+
+ private void initializeUi(final String idBoard) {
+ final WListsAdapter adapter = new WListsAdapter(this);
+ recyclerView.setAdapter(adapter);
+ recyclerView.setLayoutManager(new LinearLayoutManager(this,
+ LinearLayoutManager.HORIZONTAL, false));
+ //recyclerView.setHasFixedSize(true);
+
+ viewModel = new ViewModelProvider(this).get(WListsListViewModel.class);
+
+ viewModel.getAllWLists().observe(this, new Observer>() {
+ @Override
+ public void onChanged(List wLists) {
+ adapter.setWLists(wLists);
+ }
+ });
+
+ swipeBottomToDelete(idBoard);
+
+ /* BUG REPORT: Refresh of page enter in conflict with swipe down for delete list
+ * swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ viewModel.refresh();
+ }
+ });*/
+ }
+
+ private void swipeBottomToDelete(final String idBoard) {
+ ItemTouchHelper.SimpleCallback simpleCallback = new ItemTouchHelper.SimpleCallback(0,
+ ItemTouchHelper.DOWN) {
+ @Override
+ public boolean onMove(@NonNull RecyclerView recyclerView,
+ @NonNull RecyclerView.ViewHolder viewHolder,
+ @NonNull RecyclerView.ViewHolder target) {
+
+ return true;
+ }
+
+ @Override
+ public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
+ int pos = viewHolder.getAdapterPosition();
+ viewModel.deleteWList(pos, idBoard);
+ Toast.makeText(getApplicationContext(), "List deleted", Toast.LENGTH_LONG).show();
+
+ }
+ };
+ new ItemTouchHelper(simpleCallback).attachToRecyclerView(recyclerView);
+ }
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/adapter/BoardsListAdapter.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/adapter/BoardsListAdapter.kt
new file mode 100644
index 0000000..b62567f
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/adapter/BoardsListAdapter.kt
@@ -0,0 +1,56 @@
+package it.unisannio.ding.ids.wedroid.app.view.adapter
+
+import android.content.Context
+import android.content.Intent
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import it.unisannio.ding.ids.wedroid.app.R
+import it.unisannio.ding.ids.wedroid.app.data.entity.Board
+import it.unisannio.ding.ids.wedroid.app.view.BoardViewActivity
+
+class BoardsListAdapter internal constructor(
+ context: Context
+) : RecyclerView.Adapter() {
+
+ private val inflater = LayoutInflater.from(context)
+ private var boards = emptyList()
+
+ inner class BoardViewHolder(
+ view: View
+ ) : RecyclerView.ViewHolder(view) {
+ val boardTitle: TextView = view.findViewById(R.id.boardTitle)
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BoardViewHolder {
+ val view = inflater.inflate(R.layout.board_recycle_item, parent, false)
+ return BoardViewHolder(view)
+ }
+
+ override fun getItemCount(): Int {
+ return boards.size
+ }
+
+ override fun onBindViewHolder(holder: BoardViewHolder, position: Int) {
+ val board = boards[position]
+ holder.boardTitle.text = board.title
+
+ holder.itemView.setOnClickListener {
+
+ val intent = Intent(holder.itemView.context, BoardViewActivity::class.java)
+ intent.putExtra("idBoard", board.id)
+ holder.itemView.context.startActivity(intent)
+ }
+ }
+
+ internal fun setBoards(boards: List) {
+ this.boards = boards
+ notifyDataSetChanged()
+ }
+
+ companion object {
+ const val BOARD_ID = "BOARD_ID"
+ }
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/adapter/WListsAdapter.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/adapter/WListsAdapter.java
new file mode 100644
index 0000000..dfe929d
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/adapter/WListsAdapter.java
@@ -0,0 +1,201 @@
+package it.unisannio.ding.ids.wedroid.app.view.adapter;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import it.unisannio.ding.ids.wedroid.app.R;
+import it.unisannio.ding.ids.wedroid.app.data.entity.WList;
+import it.unisannio.ding.ids.wedroid.app.util.ServicesFactory;
+import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper;
+import it.unisannio.ding.ids.wedroid.wrapper.entity.Card;
+import it.unisannio.ding.ids.wedroid.wrapper.entity.Swimlane;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+public class WListsAdapter extends RecyclerView.Adapter {
+
+ private final LayoutInflater mInflater;
+ private List mWLists; // Cached copy of lists
+ private Context mContext;
+ private ServicesFactory service;
+ private SharedPreferenceHelper sp;
+
+ public WListsAdapter(Context context) {
+ mInflater = LayoutInflater.from(context);
+ mContext = context;
+ sp = new SharedPreferenceHelper(mContext);
+ service = new ServicesFactory(sp);
+ }
+
+ class WListViewHolder extends RecyclerView.ViewHolder {
+ private final TextView wListItemView;
+ ListView listView;
+ Button button;
+
+ private WListViewHolder(View itemView) {
+ super(itemView);
+ wListItemView = itemView.findViewById(R.id.wListTitle);
+ listView = itemView.findViewById(R.id.listViewCard);
+ button = itemView.findViewById(R.id.buttonAddCard);
+ }
+ }
+
+ @NonNull
+ @Override
+ public WListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View itemView = mInflater.inflate(R.layout.wlist_recyclerview_item, parent, false);
+ return new WListViewHolder(itemView);
+ }
+
+ @SuppressLint("SetTextI18n")
+ @Override
+ public void onBindViewHolder(@NonNull final WListViewHolder holder, int position) {
+ if (mWLists != null) {
+ final WList current = mWLists.get(position);
+ final List cardTitle = new ArrayList<>();
+ service.getCardService().getAllCards(sp.getBoardId(), current.getId()).enqueue(
+ new Callback>() {
+ @Override
+ public void onResponse(@NotNull Call> call, @NotNull Response> response) {
+ assert response.body() != null;
+ for (int i = 0; i < response.body().size(); i++) {
+ cardTitle.add(response.body().get(i).getTitle());
+ }
+ ArrayAdapter adapter = new ArrayAdapter<>(mContext,
+ android.R.layout.simple_list_item_1, cardTitle);
+ holder.listView.setAdapter(adapter);
+ holder.wListItemView.setText(current.getTitle());
+ holder.button.setText("Add Card to " + current.getTitle());
+ holder.button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ showInputDialog(current.getId());
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(@NotNull Call> call, @NotNull Throwable t) {
+ }
+ });
+
+ /*position of the old content holder**/
+
+ } else {
+ // Covers the case of data not being ready yet.
+ holder.wListItemView.setText("No wList");
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ if (mWLists != null)
+ return mWLists.size();
+ else return 0;
+ }
+
+ public void setWLists(List wList) {
+ mWLists = wList;
+ notifyDataSetChanged();
+ }
+
+ private void showInputDialog(final String current) {
+ LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+ @SuppressLint("InflateParams") View promptView = layoutInflater.inflate(
+ R.layout.alert_new_card, null);
+ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(mContext);
+ alertDialogBuilder.setView(promptView);
+
+ final EditText editText = promptView.findViewById(R.id.edittext);
+ // setup a dialog window
+ alertDialogBuilder.setCancelable(false)
+ .setPositiveButton("OK", new DialogInterface.OnClickListener() {
+
+ public void onClick(DialogInterface dialog, int id) {
+ if (!editText.getText().toString().matches("")) {
+ service.getSwimlanesService().getAllSwimlanes(sp.getBoardId())
+ .enqueue(new Callback>() {
+ @Override
+ public void onResponse(@NotNull Call> call,
+ @NotNull Response> response) {
+ String idDefaultSwimlane = null;
+ assert response.body() != null;
+ for (Swimlane swim : response.body()) {
+ if (swim.getTitle().equalsIgnoreCase("default"))
+ idDefaultSwimlane = swim.getTitle();
+ }
+ final Card card = new Card();
+ card.setTitle(editText.getText().toString());
+ card.setAuthorId(sp.getUserId());
+ card.setSwimlaneId(idDefaultSwimlane);
+ //card.setDescription("new card from app");
+ service.getListService().getList(sp.getBoardId(), current).enqueue(new Callback() {
+ @Override
+ public void onResponse(@NotNull Call call, @NotNull Response response) {
+
+ service.getCardService().newCard(sp.getBoardId(), current, card).enqueue(new Callback() {
+ @Override
+ public void onResponse(@NotNull Call call, @NotNull Response response) {
+ if (response.isSuccessful())
+ Toast.makeText(mContext, "card posted",
+ Toast.LENGTH_LONG).show();
+ else
+ Toast.makeText(mContext, "card doesn't posted",
+ Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ public void onFailure(@NotNull Call call, @NotNull Throwable t) {
+
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(@NotNull Call call, @NotNull Throwable t) {
+
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(@NotNull Call> call, @NotNull Throwable t) {
+
+ }
+ });
+ } else
+ Toast.makeText(mContext, "cancel", Toast.LENGTH_LONG).show();
+ }
+ })
+ .setNegativeButton("Cancel",
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+
+ // create an alert dialog
+ AlertDialog alert = alertDialogBuilder.create();
+ alert.show();
+ }
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/viewmodel/BoardsListViewModel.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/viewmodel/BoardsListViewModel.java
new file mode 100644
index 0000000..0d1a79e
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/viewmodel/BoardsListViewModel.java
@@ -0,0 +1,53 @@
+package it.unisannio.ding.ids.wedroid.app.viewmodel;
+
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+
+import java.util.List;
+
+import it.unisannio.ding.ids.wedroid.app.data.database.BoardDatabase;
+import it.unisannio.ding.ids.wedroid.app.data.entity.Board;
+import it.unisannio.ding.ids.wedroid.app.data.repository.BoardRepository;
+import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader;
+import it.unisannio.ding.ids.wedroid.app.util.ServicesFactory;
+import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper;
+import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardBackgroundColor;
+
+public class BoardsListViewModel extends AndroidViewModel {
+ private BoardRepository repository;
+ private LiveData> allBoards;
+
+ public BoardsListViewModel(@NonNull Application application) {
+ super(application);
+ PreferenceReader reader = new SharedPreferenceHelper(application);
+ repository = new BoardRepository(
+ BoardDatabase.getDatabase(application).boardDao(),
+ ServicesFactory.Companion.getInstance(reader).getBoardService(),
+ reader
+ );
+
+ allBoards = repository.getAllBoards();
+ }
+
+ public LiveData> getAllBoards() {
+ return allBoards;
+ }
+
+ public void insertBoard(String title, boolean isPrivate, BoardBackgroundColor color) {
+ repository.insertBoard(title, isPrivate, color);
+ }
+
+ public void deleteBoard(int position) {
+ List boards = allBoards.getValue();
+
+ if (boards != null)
+ repository.deleteBoard(boards.get(position).getId());
+ }
+
+ public void refresh() {
+ repository.synchronize();
+ }
+}
diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/viewmodel/WListsListViewModel.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/viewmodel/WListsListViewModel.java
new file mode 100644
index 0000000..e5281a5
--- /dev/null
+++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/viewmodel/WListsListViewModel.java
@@ -0,0 +1,47 @@
+package it.unisannio.ding.ids.wedroid.app.viewmodel;
+
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+
+import java.util.List;
+
+import it.unisannio.ding.ids.wedroid.app.data.database.WListDatabase;
+import it.unisannio.ding.ids.wedroid.app.data.entity.WList;
+import it.unisannio.ding.ids.wedroid.app.data.repository.WListRepository;
+import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader;
+import it.unisannio.ding.ids.wedroid.app.util.ServicesFactory;
+import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper;
+
+
+public class WListsListViewModel extends AndroidViewModel {
+ private WListRepository wListRepository;
+ private LiveData> allWLists;
+
+ public WListsListViewModel(@NonNull Application application) {
+ super(application);
+ PreferenceReader reader = new SharedPreferenceHelper(application);
+ wListRepository = new WListRepository(
+ WListDatabase.getDatabase(application).wListDao(),
+ ServicesFactory.Companion.getInstance(reader).getListService(),
+ reader);
+ allWLists = wListRepository.getAllWLists();
+ }
+
+ public LiveData> getAllWLists(){
+ return allWLists;
+ }
+
+ public void deleteWList(int position, String idBoard) {
+ List wList = allWLists.getValue();
+
+ if (wList != null)
+ wListRepository.deleteWList(idBoard, wList.get(position).getId());
+ }
+
+ public void refresh() {
+ wListRepository.synchronize();
+ }
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..1f6bb29
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_add_black_24dp.xml b/app/src/main/res/drawable/ic_add_black_24dp.xml
new file mode 100644
index 0000000..6b6146e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..0d025f9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/rounded_shape.xml b/app/src/main/res/drawable/rounded_shape.xml
new file mode 100644
index 0000000..255bc75
--- /dev/null
+++ b/app/src/main/res/drawable/rounded_shape.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_board_view.xml b/app/src/main/res/layout/activity_board_view.xml
new file mode 100644
index 0000000..04c8d00
--- /dev/null
+++ b/app/src/main/res/layout/activity_board_view.xml
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_boards_lists.xml b/app/src/main/res/layout/activity_boards_lists.xml
new file mode 100644
index 0000000..7282fa5
--- /dev/null
+++ b/app/src/main/res/layout/activity_boards_lists.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
new file mode 100644
index 0000000..0d4b0a5
--- /dev/null
+++ b/app/src/main/res/layout/activity_login.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_new_board.xml b/app/src/main/res/layout/activity_new_board.xml
new file mode 100644
index 0000000..d32de4d
--- /dev/null
+++ b/app/src/main/res/layout/activity_new_board.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_wlists_view.xml b/app/src/main/res/layout/activity_wlists_view.xml
new file mode 100644
index 0000000..539c2bc
--- /dev/null
+++ b/app/src/main/res/layout/activity_wlists_view.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/alert_new_card.xml b/app/src/main/res/layout/alert_new_card.xml
new file mode 100644
index 0000000..1ffd286
--- /dev/null
+++ b/app/src/main/res/layout/alert_new_card.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/board_recycle_item.xml b/app/src/main/res/layout/board_recycle_item.xml
new file mode 100644
index 0000000..3f472f3
--- /dev/null
+++ b/app/src/main/res/layout/board_recycle_item.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_boards_lists.xml b/app/src/main/res/layout/content_boards_lists.xml
new file mode 100644
index 0000000..dc3adf9
--- /dev/null
+++ b/app/src/main/res/layout/content_boards_lists.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_new_board.xml b/app/src/main/res/layout/content_new_board.xml
new file mode 100644
index 0000000..851e4dd
--- /dev/null
+++ b/app/src/main/res/layout/content_new_board.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/wlist_recyclerview_item.xml b/app/src/main/res/layout/wlist_recyclerview_item.xml
new file mode 100644
index 0000000..7b4a3ab
--- /dev/null
+++ b/app/src/main/res/layout/wlist_recyclerview_item.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dffca36
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dae5e08
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed465
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..14ed0af
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..69b2233
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #008577
+ #00574B
+ #D81B60
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..7ebf0d8
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,8 @@
+
+ 16dp
+
+ 10dp
+
+ 8dp
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..935d24e
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,46 @@
+
+ wedroid
+ View lists
+ Info board
+ -Description:
+ ***
+ -Members:
+ -Permission:
+ -Labels:
+ Modified at:
+ Created at:
+ Add Card
+ Enter text here...
+ Enter card name
+ BoardsListActivity
+ NewBoardActivity
+ Board name
+ Private
+ Cancel
+ Done
+ There was a problem with the name of the new board
+ It was not possible to add a new board
+ Name cannot be empty
+ The selected board has been deleted
+
+
+ - Belize
+ - Nephritis
+ - Pomegranate
+ - Pumpkin
+ - Wisteria
+ - Moderatepink
+ - Strongcyan
+ - Limegreen
+ - Midnight
+ - Dark
+ - Relax
+ - Corteza
+
+ Riempire tutti i campi
+ Formato URL non valido
+ Controlla la tua connessione internet
+ Credenziali non corrette
+ Login effettuato con successo
+
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..281db73
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/test/java/it/unisannio/ding/ids/wedroid/app/TestHelper.kt b/app/src/test/java/it/unisannio/ding/ids/wedroid/app/TestHelper.kt
new file mode 100644
index 0000000..f2a68ee
--- /dev/null
+++ b/app/src/test/java/it/unisannio/ding/ids/wedroid/app/TestHelper.kt
@@ -0,0 +1,12 @@
+package it.unisannio.ding.ids.wedroid.app
+
+import kotlin.reflect.KClass
+import kotlin.reflect.KFunction
+import kotlin.reflect.full.declaredFunctions
+import kotlin.reflect.jvm.isAccessible
+
+fun getPrivateFun(name: String, kClass: KClass<*>): KFunction<*>? {
+ return kClass.declaredFunctions
+ .find { it.name == name }
+ .also { it?.isAccessible = true }
+}
diff --git a/app/src/test/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepositoryTest.kt b/app/src/test/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepositoryTest.kt
new file mode 100644
index 0000000..d8def75
--- /dev/null
+++ b/app/src/test/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepositoryTest.kt
@@ -0,0 +1,318 @@
+package it.unisannio.ding.ids.wedroid.app.data.repository
+
+import android.util.Log
+import androidx.lifecycle.LiveData
+import io.mockk.*
+import it.unisannio.ding.ids.wedroid.app.data.dao.BoardDao
+import it.unisannio.ding.ids.wedroid.app.data.entity.Board
+import it.unisannio.ding.ids.wedroid.app.getPrivateFun
+import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader
+import it.unisannio.ding.ids.wedroid.wrapper.api.BoardService
+import junit.framework.TestCase.*
+import kotlinx.coroutines.*
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import retrofit2.Response
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import kotlin.reflect.full.callSuspend
+
+class BoardRepositoryTest {
+ private val reader = mockk()
+ private val webServer = MockWebServer()
+ private lateinit var service: BoardService
+
+ @Before
+ fun setUp() {
+ webServer.start()
+ service = Retrofit.Builder()
+ .baseUrl(webServer.url("/"))
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BoardService::class.java)
+
+ mockkStatic(Log::class)
+ every { reader.userId } returns "user id"
+ every { Log.e(any(), any()) } returns 0
+ }
+
+ @After
+ fun teardown() {
+ webServer.close()
+ }
+
+ @Test
+ fun addNewBoardsToDb() {
+ val dao = mockk()
+ var count = 0
+
+ coEvery {
+ dao.insert(any())
+ } answers {
+ count++
+ }
+
+ val addNewBoardToDb = getPrivateFun(
+ "addNewBoardToDb", BoardRepository::class
+ )
+
+ val repository = BoardRepository(
+ dao, service, reader
+ )
+
+ val board0 = Board("id0", "title0")
+ val board1 = Board("id1", "title1")
+ val board2 = Board("id2", "title2")
+
+ runBlocking {
+ addNewBoardToDb?.callSuspend(
+ repository,
+ listOf(board0, board1, board2)
+ )
+ }
+
+ coVerifyAll {
+ dao.insert(board0)
+ dao.insert(board1)
+ dao.insert(board2)
+ }
+
+ assertEquals(3, count)
+ }
+
+ @Test
+ fun insertBoardCallbackSuccess() {
+ val dao = mockk()
+ val response =
+ mockk>()
+ val board = mockk()
+
+ every { response.isSuccessful } returns true
+ every { response.body() } returns board
+ every { board.id } returns "id"
+ coEvery { dao.insert(any()) } answers {}
+
+ val insertBoard = getPrivateFun(
+ "insertBoardCallback", BoardRepository::class
+ )
+
+ val repository = BoardRepository(
+ dao, service, reader
+ )
+
+ runBlocking {
+ insertBoard?.callSuspend(
+ repository,
+ response,
+ "title"
+ )
+ }
+
+ coVerify { dao.insert(Board("id", "title")) }
+ }
+
+ @Test
+ fun insertBoardCallbackEmptyBody() {
+ val dao = mockk()
+ val response =
+ mockk>()
+
+ every { response.isSuccessful } returns true
+ every { response.body() } returns null
+
+ val insertBoard = getPrivateFun(
+ "insertBoardCallback", BoardRepository::class
+ )
+
+ val repository = BoardRepository(
+ dao, service, reader
+ )
+
+ runBlocking {
+ insertBoard?.callSuspend(
+ repository,
+ response,
+ "title"
+ )
+ }
+
+ verify { Log.e("RETROFIT", "empty body") }
+ }
+
+ @Test
+ fun insertBoardCallbackError() {
+ val dao = mockk()
+ val response =
+ mockk>()
+
+ every { response.isSuccessful } returns false
+ every { response.code() } returns 400
+ every { response.message() } returns "Error"
+
+ val insertBoard = getPrivateFun(
+ "insertBoardCallback", BoardRepository::class
+ )
+
+ val repository = BoardRepository(
+ dao, service, reader
+ )
+
+ runBlocking {
+ insertBoard?.callSuspend(
+ repository,
+ response,
+ "title"
+ )
+ }
+
+ verify { Log.e("RETROFIT", "400 Error") }
+ }
+
+ @Test
+ fun deleteBoardCallbackSuccess() {
+ val dao = mockk()
+ val response =
+ mockk>()
+
+ every { response.isSuccessful } returns true
+ coEvery { dao.delete(any()) } answers {}
+
+ val deleteBoard = getPrivateFun(
+ "deleteBoardCallback", BoardRepository::class
+ )
+
+ val repository = BoardRepository(
+ dao, service, reader
+ )
+
+ runBlocking {
+ deleteBoard?.callSuspend(
+ repository,
+ response,
+ "id"
+ )
+ }
+
+ coVerify { dao.delete(Board("id")) }
+ }
+
+ @Test
+ fun deleteBoardCallbackError() {
+ val dao = mockk()
+ val response =
+ mockk>()
+
+ every { response.isSuccessful } returns false
+ every { response.code() } returns 400
+ every { response.message() } returns "Error"
+
+ val deleteBoard = getPrivateFun(
+ "deleteBoardCallback", BoardRepository::class
+ )
+
+ val repository = BoardRepository(
+ dao, service, reader
+ )
+
+ runBlocking {
+ deleteBoard?.callSuspend(
+ repository,
+ response,
+ "id"
+ )
+ }
+
+ verify { Log.e("RETROFIT", "400 Error") }
+ }
+
+ @Test
+ fun deleteOldBoardFromDb() {
+ val dao = mockk()
+ val dbBoards = mockk>>()
+
+ val board0 = Board("id0", "title0")
+ val board1 = Board("id1", "title1")
+ val board2 = Board("id2", "title2")
+ val board3 = Board("id2", "title3")
+
+ every { dbBoards.value } returns listOf(
+ board0, board1, board2, board3
+ )
+ coEvery { dao.getAllBoard() } returns dbBoards
+ coEvery { dao.delete(any()) } answers {}
+
+ val removeOldBoards = getPrivateFun(
+ "removeOldBoardsFromDb", BoardRepository::class
+ )
+
+ val repository = BoardRepository(
+ dao, service, reader
+ )
+
+ runBlocking {
+ removeOldBoards?.callSuspend(
+ repository,
+ listOf(
+ board0, board1, board3
+ )
+ )
+ }
+
+ coVerify { dao.delete(board2) }
+ }
+
+ @Test
+ fun synchronizeCallbackError() {
+ val dao = mockk()
+ val response =
+ mockk>>()
+
+ every { response.isSuccessful } returns false
+ every { response.code() } returns 400
+ every { response.message() } returns "Error"
+
+ val synchronize = getPrivateFun(
+ "synchronizeCallback", BoardRepository::class
+ )
+
+ val repository = BoardRepository(
+ dao, service, reader
+ )
+
+ runBlocking {
+ synchronize?.callSuspend(
+ repository,
+ response
+ )
+ }
+
+ verify { Log.e("RETROFIT", "400 Error") }
+ }
+
+ @Test
+ fun synchronizeCallbackEmptyBody() {
+ val dao = mockk()
+ val response =
+ mockk>>()
+
+ every { response.isSuccessful } returns true
+ every { response.body() } returns null
+
+ val synchronize = getPrivateFun(
+ "synchronizeCallback", BoardRepository::class
+ )
+
+ val repository = BoardRepository(
+ dao, service, reader
+ )
+
+ runBlocking {
+ synchronize?.callSuspend(
+ repository,
+ response
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/it/unisannio/ding/ids/wedroid/app/data/repository/WListRepositoryTest.kt b/app/src/test/java/it/unisannio/ding/ids/wedroid/app/data/repository/WListRepositoryTest.kt
new file mode 100644
index 0000000..93828ed
--- /dev/null
+++ b/app/src/test/java/it/unisannio/ding/ids/wedroid/app/data/repository/WListRepositoryTest.kt
@@ -0,0 +1,203 @@
+package it.unisannio.ding.ids.wedroid.app.data.repository
+
+import android.util.Log
+import androidx.lifecycle.LiveData
+import io.mockk.*
+import it.unisannio.ding.ids.wedroid.app.data.dao.WListDao
+import it.unisannio.ding.ids.wedroid.app.data.entity.WList
+import it.unisannio.ding.ids.wedroid.app.getPrivateFun
+import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader
+import it.unisannio.ding.ids.wedroid.wrapper.api.ListService
+import junit.framework.TestCase
+import kotlinx.coroutines.runBlocking
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import retrofit2.Response
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import kotlin.reflect.full.callSuspend
+
+class WListRepositoryTest {
+ private val reader = mockk()
+ private val webServer = MockWebServer()
+ private lateinit var service: ListService
+
+ @Before
+ fun setUp() {
+ webServer.start()
+ service = Retrofit.Builder()
+ .baseUrl(webServer.url("/"))
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(ListService::class.java)
+
+ mockkStatic(Log::class)
+ every { reader.boardId } returns "board id"
+ every { Log.e(any(), any()) } returns 0
+ }
+
+ @After
+ fun teardown() {
+ webServer.close()
+ }
+
+ @Test
+ fun addNewBoardsToDb() {
+ val dao = mockk()
+ var count = 0
+
+ coEvery {
+ dao.insert(any())
+ } answers {
+ count++
+ }
+
+ val addNewListToDb = getPrivateFun(
+ "addNewWListToDb", WListRepository::class
+ )
+
+ val repository = WListRepository(
+ dao, service, reader
+ )
+
+ val list0 = WList("id0", "title0")
+ val list1 = WList("id1", "title1")
+ val list2 = WList("id2", "title2")
+
+ runBlocking {
+ addNewListToDb?.callSuspend(
+ repository,
+ listOf(list0, list1, list2)
+ )
+ }
+
+ coVerifyAll {
+ dao.insert(list0)
+ dao.insert(list1)
+ dao.insert(list2)
+ }
+
+ TestCase.assertEquals(3, count)
+ }
+
+ @Test
+ fun deleteWListCallbackSuccess() {
+ val dao = mockk()
+ val response =
+ mockk>()
+
+ every { response.isSuccessful } returns true
+ coEvery { dao.delete(any()) } answers {}
+
+ val deleteWList = getPrivateFun(
+ "deleteWListCallBack", WListRepository::class
+ )
+
+ val repository = WListRepository(
+ dao, service, reader
+ )
+
+ runBlocking {
+ deleteWList?.callSuspend(
+ repository,
+ response,
+ "id"
+ )
+ }
+
+ coVerify { dao.delete(WList("id")) }
+ }
+
+ @Test
+ fun deleteWListCallbackError() {
+ val dao = mockk()
+ val response = mockk>()
+
+ every { response.isSuccessful } returns false
+ every { response.code() } returns 400
+ every { response.message() } returns "Error"
+
+ val repository = WListRepository(dao, service, reader)
+
+ val deleteWList = getPrivateFun(
+ "deleteWListCallBack", WListRepository::class
+ )
+
+ runBlocking {
+ deleteWList?.callSuspend(
+ repository,
+ response,
+ "id"
+ )
+ }
+ verify { Log.e("RETROFIT", "400 Error") }
+ }
+
+ @Test
+ fun deleteOldWListFromDb() {
+ val dao = mockk()
+ val dbWLists = mockk>>()
+
+ val wList0 = WList("id0", "title0")
+ val wList1 = WList("id1", "title1")
+ val wList2 = WList("id2", "title2")
+ val wList3 = WList("id2", "title3")
+
+ every { dbWLists.value } returns listOf(
+ wList0, wList1, wList2, wList3
+ )
+ coEvery { dao.allWList } returns dbWLists
+ coEvery { dao.delete(any()) } answers {}
+
+ val removeOldWLists = getPrivateFun(
+ "removeOldWListsFromDb", WListRepository::class
+ )
+
+ val repository = WListRepository(
+ dao, service, reader
+ )
+
+ runBlocking {
+ removeOldWLists?.callSuspend(
+ repository,
+ listOf(
+ wList0, wList2, wList3
+ )
+ )
+ }
+
+ coVerify { dao.delete(wList1) }
+ }
+
+ @Test
+ fun synchronizeCallbackError() {
+ val dao = mockk()
+
+ val response =
+ mockk>>()
+
+ every { response.isSuccessful } returns false
+ every { response.code() } returns 400
+ every { response.message() } returns "Error"
+
+ val synchronize = getPrivateFun(
+ "synchronizeCallback", WListRepository::class
+ )
+
+ val repository = WListRepository(
+ dao, service, reader
+ )
+
+ runBlocking {
+ synchronize?.callSuspend(
+ repository,
+ response
+ )
+ }
+
+ verify { Log.e("RETROFIT", "400 Error") }
+ }
+
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..1038f12
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,36 @@
+buildscript {
+ ext.kotlin_version = '1.3.60'
+ repositories {
+ google()
+ jcenter()
+ maven { url "https://plugins.gradle.org/m2/" }
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.5.3'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.2.2"
+ }
+}
+
+allprojects {
+ apply plugin: 'checkstyle'
+
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+ext {
+ roomVersion = '2.2.3'
+ archLifecycleVersion = '2.2.0-rc03'
+ androidxArchVersion = '2.1.0'
+ coreTestingVersion = "2.1.0"
+ coroutines = '1.3.2'
+ materialVersion = "1.0.0"
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000..54f31f4
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml
new file mode 100644
index 0000000..05426c6
--- /dev/null
+++ b/config/detekt/detekt.yml
@@ -0,0 +1,580 @@
+build:
+ maxIssues: 10
+ weights:
+ # complexity: 2
+ # LongParameterList: 1
+ # style: 1
+ # comments: 1
+
+config:
+ validation: true
+ # when writing own rules with new properties, exclude the property path e.g.: "my_rule_set,.*>.*>[my_property]"
+ excludes: ""
+
+processors:
+ active: true
+ exclude:
+ # - 'DetektProgressListener'
+ # - 'FunctionCountProcessor'
+ # - 'PropertyCountProcessor'
+ # - 'ClassCountProcessor'
+ # - 'PackageCountProcessor'
+ # - 'KtFileCountProcessor'
+
+console-reports:
+ active: true
+ exclude:
+ # - 'ProjectStatisticsReport'
+ # - 'ComplexityReport'
+ # - 'NotificationReport'
+ # - 'FindingsReport'
+ - 'FileBasedFindingsReport'
+ # - 'BuildFailureReport'
+
+comments:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ CommentOverPrivateFunction:
+ active: false
+ CommentOverPrivateProperty:
+ active: false
+ EndOfSentenceFormat:
+ active: false
+ endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!:]$)
+ UndocumentedPublicClass:
+ active: false
+ searchInNestedClass: true
+ searchInInnerClass: true
+ searchInInnerObject: true
+ searchInInnerInterface: true
+ UndocumentedPublicFunction:
+ active: false
+ UndocumentedPublicProperty:
+ active: false
+
+complexity:
+ active: true
+ ComplexCondition:
+ active: true
+ threshold: 4
+ ComplexInterface:
+ active: false
+ threshold: 10
+ includeStaticDeclarations: false
+ ComplexMethod:
+ active: true
+ threshold: 15
+ ignoreSingleWhenExpression: false
+ ignoreSimpleWhenEntries: false
+ ignoreNestingFunctions: false
+ nestingFunctions: run,let,apply,with,also,use,forEach,isNotNull,ifNull
+ LabeledExpression:
+ active: false
+ ignoredLabels: ""
+ LargeClass:
+ active: true
+ threshold: 600
+ LongMethod:
+ active: true
+ threshold: 60
+ LongParameterList:
+ active: true
+ threshold: 6
+ ignoreDefaultParameters: false
+ MethodOverloading:
+ active: false
+ threshold: 6
+ NestedBlockDepth:
+ active: true
+ threshold: 4
+ StringLiteralDuplication:
+ active: false
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ threshold: 3
+ ignoreAnnotation: true
+ excludeStringsWithLessThan5Characters: true
+ ignoreStringsRegex: '$^'
+ TooManyFunctions:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ thresholdInFiles: 11
+ thresholdInClasses: 11
+ thresholdInInterfaces: 11
+ thresholdInObjects: 11
+ thresholdInEnums: 11
+ ignoreDeprecated: false
+ ignorePrivate: false
+ ignoreOverridden: false
+
+empty-blocks:
+ active: true
+ EmptyCatchBlock:
+ active: true
+ allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
+ EmptyClassBlock:
+ active: true
+ EmptyDefaultConstructor:
+ active: true
+ EmptyDoWhileBlock:
+ active: true
+ EmptyElseBlock:
+ active: true
+ EmptyFinallyBlock:
+ active: true
+ EmptyForBlock:
+ active: true
+ EmptyFunctionBlock:
+ active: true
+ ignoreOverriddenFunctions: false
+ EmptyIfBlock:
+ active: true
+ EmptyInitBlock:
+ active: true
+ EmptyKtFile:
+ active: true
+ EmptySecondaryConstructor:
+ active: true
+ EmptyWhenBlock:
+ active: true
+ EmptyWhileBlock:
+ active: true
+
+exceptions:
+ active: true
+ ExceptionRaisedInUnexpectedLocation:
+ active: false
+ methodNames: 'toString,hashCode,equals,finalize'
+ InstanceOfCheckForException:
+ active: false
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ NotImplementedDeclaration:
+ active: false
+ PrintStackTrace:
+ active: false
+ RethrowCaughtException:
+ active: false
+ ReturnFromFinally:
+ active: false
+ ignoreLabeled: false
+ SwallowedException:
+ active: false
+ ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException'
+ allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
+ ThrowingExceptionFromFinally:
+ active: false
+ ThrowingExceptionInMain:
+ active: false
+ ThrowingExceptionsWithoutMessageOrCause:
+ active: false
+ exceptions: 'IllegalArgumentException,IllegalStateException,IOException'
+ ThrowingNewInstanceOfSameException:
+ active: false
+ TooGenericExceptionCaught:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ exceptionNames:
+ - ArrayIndexOutOfBoundsException
+ - Error
+ - Exception
+ - IllegalMonitorStateException
+ - NullPointerException
+ - IndexOutOfBoundsException
+ - RuntimeException
+ - Throwable
+ allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
+ TooGenericExceptionThrown:
+ active: true
+ exceptionNames:
+ - Error
+ - Exception
+ - Throwable
+ - RuntimeException
+
+formatting:
+ active: true
+ android: true
+ autoCorrect: true
+ AnnotationOnSeparateLine:
+ active: false
+ autoCorrect: true
+ ChainWrapping:
+ active: true
+ autoCorrect: true
+ CommentSpacing:
+ active: true
+ autoCorrect: true
+ Filename:
+ active: true
+ FinalNewline:
+ active: false
+ autoCorrect: true
+ ImportOrdering:
+ active: false
+ autoCorrect: true
+ Indentation:
+ active: false
+ autoCorrect: true
+ indentSize: 4
+ continuationIndentSize: 4
+ MaximumLineLength:
+ active: true
+ maxLineLength: 120
+ ModifierOrdering:
+ active: true
+ autoCorrect: true
+ MultiLineIfElse:
+ active: false
+ autoCorrect: true
+ NoBlankLineBeforeRbrace:
+ active: true
+ autoCorrect: true
+ NoConsecutiveBlankLines:
+ active: true
+ autoCorrect: true
+ NoEmptyClassBody:
+ active: true
+ autoCorrect: true
+ NoLineBreakAfterElse:
+ active: true
+ autoCorrect: true
+ NoLineBreakBeforeAssignment:
+ active: true
+ autoCorrect: true
+ NoMultipleSpaces:
+ active: true
+ autoCorrect: true
+ NoSemicolons:
+ active: true
+ autoCorrect: true
+ NoTrailingSpaces:
+ active: true
+ autoCorrect: true
+ NoUnitReturn:
+ active: true
+ autoCorrect: true
+ NoUnusedImports:
+ active: true
+ autoCorrect: true
+ NoWildcardImports:
+ active: true
+ PackageName:
+ active: true
+ autoCorrect: true
+ ParameterListWrapping:
+ active: true
+ autoCorrect: true
+ indentSize: 4
+ SpacingAroundColon:
+ active: true
+ autoCorrect: true
+ SpacingAroundComma:
+ active: true
+ autoCorrect: true
+ SpacingAroundCurly:
+ active: true
+ autoCorrect: true
+ SpacingAroundDot:
+ active: true
+ autoCorrect: true
+ SpacingAroundKeyword:
+ active: true
+ autoCorrect: true
+ SpacingAroundOperators:
+ active: true
+ autoCorrect: true
+ SpacingAroundParens:
+ active: true
+ autoCorrect: true
+ SpacingAroundRangeOperator:
+ active: true
+ autoCorrect: true
+ StringTemplate:
+ active: true
+ autoCorrect: true
+
+naming:
+ active: true
+ ClassNaming:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ classPattern: '[A-Z$][a-zA-Z0-9$]*'
+ ConstructorParameterNaming:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ parameterPattern: '[a-z][A-Za-z0-9]*'
+ privateParameterPattern: '[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ ignoreOverridden: true
+ EnumNaming:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*'
+ ForbiddenClassName:
+ active: false
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ forbiddenName: ''
+ FunctionMaxLength:
+ active: false
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ maximumFunctionNameLength: 30
+ FunctionMinLength:
+ active: false
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ minimumFunctionNameLength: 3
+ FunctionNaming:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
+ excludeClassPattern: '$^'
+ ignoreOverridden: true
+ FunctionParameterNaming:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ parameterPattern: '[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ ignoreOverriddenFunctions: true
+ InvalidPackageDeclaration:
+ active: false
+ rootPackage: ''
+ MatchingDeclarationName:
+ active: true
+ MemberNameEqualsClassName:
+ active: true
+ ignoreOverriddenFunction: true
+ ObjectPropertyNaming:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ constantPattern: '[A-Za-z][_A-Za-z0-9]*'
+ propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
+ privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
+ PackageNaming:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$'
+ TopLevelPropertyNaming:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ constantPattern: '[A-Z][_A-Z0-9]*'
+ propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
+ privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
+ VariableMaxLength:
+ active: false
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ maximumVariableNameLength: 64
+ VariableMinLength:
+ active: false
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ minimumVariableNameLength: 1
+ VariableNaming:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ variablePattern: '[a-z][A-Za-z0-9]*'
+ privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ ignoreOverridden: true
+
+performance:
+ active: true
+ ArrayPrimitive:
+ active: true
+ ForEachOnRange:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ SpreadOperator:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ UnnecessaryTemporaryInstantiation:
+ active: true
+
+potential-bugs:
+ active: true
+ Deprecation:
+ active: false
+ DuplicateCaseInWhenExpression:
+ active: true
+ EqualsAlwaysReturnsTrueOrFalse:
+ active: true
+ EqualsWithHashCodeExist:
+ active: true
+ ExplicitGarbageCollectionCall:
+ active: true
+ HasPlatformType:
+ active: false
+ ImplicitDefaultLocale:
+ active: false
+ InvalidRange:
+ active: true
+ IteratorHasNextCallsNextMethod:
+ active: true
+ IteratorNotThrowingNoSuchElementException:
+ active: true
+ LateinitUsage:
+ active: false
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ excludeAnnotatedProperties: ""
+ ignoreOnClassesPattern: ""
+ MissingWhenCase:
+ active: true
+ RedundantElseInWhen:
+ active: true
+ UnconditionalJumpStatementInLoop:
+ active: false
+ UnreachableCode:
+ active: true
+ UnsafeCallOnNullableType:
+ active: true
+ UnsafeCast:
+ active: false
+ UselessPostfixExpression:
+ active: false
+ WrongEqualsTypeParameter:
+ active: true
+
+style:
+ active: true
+ CollapsibleIfStatements:
+ active: false
+ DataClassContainsFunctions:
+ active: false
+ conversionFunctionPrefix: 'to'
+ DataClassShouldBeImmutable:
+ active: false
+ EqualsNullCall:
+ active: true
+ EqualsOnSignatureLine:
+ active: false
+ ExplicitItLambdaParameter:
+ active: false
+ ExpressionBodySyntax:
+ active: false
+ includeLineWrapping: false
+ ForbiddenComment:
+ active: true
+ values: 'TODO:,FIXME:,STOPSHIP:'
+ allowedPatterns: ""
+ ForbiddenImport:
+ active: false
+ imports: ''
+ forbiddenPatterns: ""
+ ForbiddenVoid:
+ active: false
+ ignoreOverridden: false
+ ignoreUsageInGenerics: false
+ FunctionOnlyReturningConstant:
+ active: true
+ ignoreOverridableFunction: true
+ excludedFunctions: 'describeContents'
+ excludeAnnotatedFunction: "dagger.Provides"
+ LibraryCodeMustSpecifyReturnType:
+ active: true
+ LoopWithTooManyJumpStatements:
+ active: true
+ maxJumpCount: 1
+ MagicNumber:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ ignoreNumbers: '-1,0,1,2'
+ ignoreHashCodeFunction: true
+ ignorePropertyDeclaration: false
+ ignoreLocalVariableDeclaration: false
+ ignoreConstantDeclaration: true
+ ignoreCompanionObjectPropertyDeclaration: true
+ ignoreAnnotation: false
+ ignoreNamedArgument: true
+ ignoreEnums: false
+ ignoreRanges: false
+ MandatoryBracesIfStatements:
+ active: false
+ MaxLineLength:
+ active: true
+ maxLineLength: 120
+ excludePackageStatements: true
+ excludeImportStatements: true
+ excludeCommentStatements: false
+ MayBeConst:
+ active: true
+ ModifierOrder:
+ active: true
+ NestedClassesVisibility:
+ active: false
+ NewLineAtEndOfFile:
+ active: true
+ NoTabs:
+ active: false
+ OptionalAbstractKeyword:
+ active: true
+ OptionalUnit:
+ active: false
+ OptionalWhenBraces:
+ active: false
+ PreferToOverPairSyntax:
+ active: false
+ ProtectedMemberInFinalClass:
+ active: true
+ RedundantExplicitType:
+ active: false
+ RedundantVisibilityModifierRule:
+ active: false
+ ReturnCount:
+ active: true
+ max: 2
+ excludedFunctions: "equals"
+ excludeLabeled: false
+ excludeReturnFromLambda: true
+ excludeGuardClauses: false
+ SafeCast:
+ active: true
+ SerialVersionUIDInSerializableClass:
+ active: false
+ SpacingBetweenPackageAndImports:
+ active: false
+ ThrowsCount:
+ active: true
+ max: 2
+ TrailingWhitespace:
+ active: false
+ UnderscoresInNumericLiterals:
+ active: false
+ acceptableDecimalLength: 5
+ UnnecessaryAbstractClass:
+ active: true
+ excludeAnnotatedClasses: "dagger.Module"
+ UnnecessaryApply:
+ active: false
+ UnnecessaryInheritance:
+ active: true
+ UnnecessaryLet:
+ active: false
+ UnnecessaryParentheses:
+ active: false
+ UntilInsteadOfRangeTo:
+ active: false
+ UnusedImports:
+ active: false
+ UnusedPrivateClass:
+ active: true
+ UnusedPrivateMember:
+ active: false
+ allowedNames: "(_|ignored|expected|serialVersionUID)"
+ UseArrayLiteralsInAnnotations:
+ active: false
+ UseCheckOrError:
+ active: false
+ UseDataClass:
+ active: false
+ excludeAnnotatedClasses: ""
+ allowVars: false
+ UseIfInsteadOfWhen:
+ active: false
+ UseRequire:
+ active: false
+ UselessCallOnNotNull:
+ active: true
+ UtilityClassWithPublicConstructor:
+ active: true
+ VarCouldBeVal:
+ active: false
+ WildcardImport:
+ active: true
+ excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ excludeImports: 'java.util.*,kotlinx.android.synthetic.*'
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..23339e0
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..d3fde33
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun Nov 24 17:58:41 CET 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..66df284
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+include ':app', ':wrapper'
+rootProject.name='wedroid'
diff --git a/wrapper/build.gradle.kts b/wrapper/build.gradle.kts
new file mode 100644
index 0000000..6d3203c
--- /dev/null
+++ b/wrapper/build.gradle.kts
@@ -0,0 +1,32 @@
+plugins {
+ `java-library`
+}
+
+dependencies {
+ // use retrofit
+ val retrofitVersion = "2.6.2"
+ implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
+ implementation("com.squareup.retrofit2:converter-gson:$retrofitVersion")
+
+ // mock server response
+ testImplementation("com.squareup.okhttp3:mockwebserver:4.2.1")
+ // use JUnit test framework
+ testImplementation("junit:junit:4.12")
+}
+
+tasks{
+ register("fatJar") {
+ archiveClassifier.set("fat")
+
+ from(
+ configurations.compileClasspath
+ .map { config ->
+ config.map { if (it.isDirectory) it else zipTree(it) }
+ }
+ )
+
+ manifest {
+ attributes("Implementation-Title" to "wedroid-wrapper")
+ }
+ }
+}
diff --git a/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/BoardService.java b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/BoardService.java
new file mode 100644
index 0000000..bed0174
--- /dev/null
+++ b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/BoardService.java
@@ -0,0 +1,90 @@
+package it.unisannio.ding.ids.wedroid.wrapper.api;
+
+import it.unisannio.ding.ids.wedroid.wrapper.entity.Board;
+import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardPrototype;
+import it.unisannio.ding.ids.wedroid.wrapper.entity.LabelPrototype;
+import it.unisannio.ding.ids.wedroid.wrapper.entity.MemberPermission;
+import java.util.List;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.DELETE;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.PUT;
+import retrofit2.http.Path;
+
+public interface BoardService {
+
+ /**
+ * Get all public boards.
+ *
+ * @return the list with all public boards
+ */
+ @GET("api/boards")
+ Call> getPublicBoards();
+
+ /**
+ * Create a board.
+ *
+ * @param boardPrototype the prototype of the new board
+ * @return the new board
+ * @see BoardPrototype
+ */
+ @POST("api/boards")
+ Call newBoard(@Body BoardPrototype boardPrototype);
+
+ /**
+ * Get the board with that particular ID.
+ *
+ * @param boardId The ID of the board
+ * @return The board with the matching ID
+ */
+ @GET("api/boards/{boardId}")
+ Call getBoard(@Path("boardId") String boardId);
+
+ /**
+ * Delete the board with that particular ID
+ *
+ * @param boardId The ID of the board
+ */
+ @DELETE("api/boards/{boardId}")
+ Call deleteBoard(@Path("boardId") String boardId);
+
+ /**
+ * Add a label to a board.
+ *
+ * @param boardId The ID of the board
+ * @param labelPrototype The prototype of new label
+ * @return The ID of new label
+ * @see LabelPrototype
+ */
+ @PUT("/api/boards/{boardId}/labels")
+ Call addLabel(
+ @Path("boardId") String boardId,
+ @Body LabelPrototype labelPrototype
+ );
+
+ /**
+ * Change the permission of a member of a board.
+ *
+ * @param boardId The ID of the board
+ * @param memberId The ID of the member
+ * @param memberPermission The new role of the member
+ * @see MemberPermission
+ */
+ @POST("/api/boards/{boardId}/members/{memberId}")
+ Call setMemberPermission(
+ @Path("boardId") String boardId,
+ @Path("memberId") String memberId,
+ @Body MemberPermission memberPermission
+ );
+
+ /**
+ * Get all boards attached to a user.
+ *
+ * @param userId The ID of the user
+ * @return The list with all user's boards
+ */
+ @GET("api/users/{userId}/boards")
+ Call> getBoardsFromUser(@Path("userId") String userId);
+}
diff --git a/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/CardCommentService.java b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/CardCommentService.java
new file mode 100644
index 0000000..232c28c
--- /dev/null
+++ b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/CardCommentService.java
@@ -0,0 +1,74 @@
+package it.unisannio.ding.ids.wedroid.wrapper.api;
+
+import it.unisannio.ding.ids.wedroid.wrapper.entity.Comment;
+import java.util.List;
+import retrofit2.Call;
+import retrofit2.http.DELETE;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.Path;
+
+public interface CardCommentService {
+
+ /**
+ * Get all comments attached to a card.
+ *
+ * @param boardId The ID of the board
+ * @param cardId The ID of the card
+ * @return The list of the comments
+ */
+ @GET("/api/boards/{boardId}/cards/{cardId}/comments")
+ Call> getAllComments(
+ @Path("boardId") String boardId,
+ @Path("cardId") String cardId
+ );
+
+ /**
+ * Post new comment attached on the give card.
+ *
+ * @param boardId The ID of the board
+ * @param cardId The ID of the card
+ * @param authorId The ID of the author
+ * @param comment The comment
+ * @return The id of the new comment
+ */
+ @FormUrlEncoded
+ @POST("/api/boards/{boardId}/cards/{cardId}/comments")
+ Call postNewComment(
+ @Path("boardId") String boardId,
+ @Path("cardId") String cardId,
+ @Field("authorId") String authorId,
+ @Field("comment") String comment
+ );
+
+ /**
+ * Get a comment attached to a card.
+ *
+ * @param boardId The ID of the board
+ * @param cardId The ID of the card
+ * @param commentId The ID of the comment
+ * @return The comment
+ */
+ @GET("/api/boards/{boardId}/cards/{cardId}/comments/{commentId}")
+ Call getComment(
+ @Path("boardId") String boardId,
+ @Path("cardId") String cardId,
+ @Path("commentId") String commentId
+ );
+
+ /**
+ * Delete a comment attached to a card.
+ *
+ * @param boardId The ID of the board
+ * @param cardId The ID of the card
+ * @param commentId The ID of the comment
+ */
+ @DELETE("/api/boards/{boardId}/cards/{cardId}/comments/{commentId}")
+ Call deleteComment(
+ @Path("boardId") String boardId,
+ @Path("cardId") String cardId,
+ @Path("commentId") String commentId
+ );
+}
diff --git a/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/CardService.java b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/CardService.java
new file mode 100644
index 0000000..22c4c75
--- /dev/null
+++ b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/CardService.java
@@ -0,0 +1,99 @@
+package it.unisannio.ding.ids.wedroid.wrapper.api;
+
+import it.unisannio.ding.ids.wedroid.wrapper.entity.Card;
+import java.util.List;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.DELETE;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.Headers;
+import retrofit2.http.POST;
+import retrofit2.http.PUT;
+import retrofit2.http.Path;
+
+public interface CardService {
+
+ /**
+ * Get all cards belonging to a list
+ *
+ * @param boardID The board ID of cards
+ * @param lidtID The list ID of cards
+ * @return list of cards in the list
+ */
+ @GET("/api/boards/{board}/lists/{list}/cards")
+ Call> getAllCards(@Path("board") String boardID, @Path("list") String lidtID);
+
+ /**
+ * Delete a card
+ *
+ * @param boardID The board ID of card
+ * @param listID The list ID of card
+ * @param cardID The card ID
+ * @return void
+ */
+ @DELETE("/api/boards/{board}/lists/{list}/cards/{card}")
+ Call deleteCard(@Path("board") String boardID, @Path("list") String listID, @Path("card") String cardID);
+
+ /**
+ *
+ * @param card new Card
+ * @param boardID The ID of the board destination
+ * @param listID The ID of the list destination
+ * @return the card with matching ID
+ */
+ @POST("/api/boards/{board}/lists/{list}/cards")
+ Call newCard(@Path("board") String boardID, @Path("list") String listID, @Body Card card);
+
+ /**
+ * Get information card
+ * @param boardID the ID of the board
+ * @param listID the ID of the list
+ * @param cardID the ID of the card
+ * @return card body
+ */
+ @GET("/api/boards/{board}/lists/{list}/cards/{card}")
+ Call getCard(@Path("board") String boardID, @Path("list") String listID, @Path("card") String cardID);
+
+ /**
+ * Get list of cards by swinlaneID
+ * @param boardID the ID of the board
+ * @param swimlaneID the ID of the swimlane
+ * @return list of swimlane cards
+ */
+ @GET("/api/boards/{board}/swimlanes/{swimlane}/cards")
+ Call> getCardsForswimlane(@Path("board") String boardID, @Path("swimlane") String swimlaneID);
+
+ /**
+ * Update a card
+ *
+ * @param card params to update
+ * @param boardID the id of the board
+ * @param listID the id of the list
+ * @param cardID the id of the card to modify
+ * @return The card with the matching ID
+ */
+ @Headers("Content-Type: application/merge-patch+json")
+ @PUT("/api/boards/{board}/lists/{list}/cards/{card}")
+ Call putCard(@Path("board") String boardID, @Path("list") String listID,
+ @Path("card") String cardID, @Body Card card);
+
+ /**
+ * Move a card from a list to an other
+ *
+ * @param boardID The id of the Board
+ * @param oldListID The id of the source list
+ * @param cardID The id of the card
+ * @param newListId The id of the destination list
+ * @return The card with the matching ID
+ */
+ @FormUrlEncoded
+ @PUT("/api/boards/{board}/lists/{list}/cards/{card}")
+ Call moveCard(
+ @Path("board") String boardID,
+ @Path("list") String oldListID,
+ @Path("card") String cardID,
+ @Field("listId") String newListId
+ );
+}
diff --git a/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/ChecklistService.java b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/ChecklistService.java
new file mode 100644
index 0000000..6aecf68
--- /dev/null
+++ b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/ChecklistService.java
@@ -0,0 +1,168 @@
+package it.unisannio.ding.ids.wedroid.wrapper.api;
+
+import it.unisannio.ding.ids.wedroid.wrapper.entity.Checklist;
+import it.unisannio.ding.ids.wedroid.wrapper.entity.ChecklistItem;
+import it.unisannio.ding.ids.wedroid.wrapper.entity.ChecklistItemStatus;
+import it.unisannio.ding.ids.wedroid.wrapper.entity.ChecklistPrototype;
+import java.util.List;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.DELETE;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.PUT;
+import retrofit2.http.Path;
+
+public interface ChecklistService {
+
+ /**
+ * Get all checklists attached on a card.
+ *
+ * @param boardId The ID of the board
+ * @param cardId The ID of the card
+ * @return The list with the checklists
+ */
+ @GET("/api/boards/{boardId}/cards/{cardId}/checklists")
+ Call> getAllChecklists(
+ @Path("boardId") String boardId,
+ @Path("cardId") String cardId
+ );
+
+ /**
+ * Attach new empty checklist to a card.
+ *
+ * @param boardId The ID of the board
+ * @param cardId The ID of the card
+ * @param title The title of the new checklist
+ * @return The new checklist
+ */
+ @FormUrlEncoded
+ @POST("/api/boards/{boardId}/cards/{cardId}/checklists")
+ Call postChecklist(
+ @Path("boardId") String boardId,
+ @Path("cardId") String cardId,
+ @Field("title") String title
+ );
+
+ /**
+ * Attach new checklist to a card.
+ *
+ * @param boardId The ID of the board
+ * @param cardId The ID of the card
+ * @param checklistPrototype The prototype of the checklist
+ * @return The new checklist
+ * @see ChecklistPrototype
+ */
+ @POST("/api/boards/{boardId}/cards/{cardId}/checklists")
+ Call postChecklist(
+ @Path("boardId") String boardId,
+ @Path("cardId") String cardId,
+ @Body ChecklistPrototype checklistPrototype
+ );
+
+ /**
+ * Get the checklist attached on a card.
+ *
+ * @param boardId The ID of the board
+ * @param cardId The ID of the card
+ * @param checklistId The ID of the checklist
+ * @return The checklist
+ */
+ @GET("/api/boards/{boardId}/cards/{cardId}/checklists/{checklistId}")
+ Call getChecklist(
+ @Path("boardId") String boardId,
+ @Path("cardId") String cardId,
+ @Path("checklistId") String checklistId
+ );
+
+ /**
+ * Delete the checklist attached on a card.
+ *
+ * @param boardId The ID of the board
+ * @param cardId The ID of the card
+ * @param checklistId The ID of the checklist
+ * @return The id of the checklist
+ */
+ @DELETE("/api/boards/{boardId}/cards/{cardId}/checklists/{checklistId}")
+ Call deleteChecklist(
+ @Path("boardId") String boardId,
+ @Path("cardId") String cardId,
+ @Path("checklistId") String checklistId
+ );
+
+ /**
+ * Get the checklist item attached on a checklist.
+ *
+ * @param boardId The ID of the board
+ * @param cardId The ID of the card
+ * @param checklistId The ID of the checklist
+ * @param itemId The ID of the item
+ * @return The checklist item
+ */
+ @GET("/api/boards/{boardId}/cards/{cardId}/checklists/{checklistId}/items/{itemId}")
+ Call getChecklistItem(
+ @Path("boardId") String boardId,
+ @Path("cardId") String cardId,
+ @Path("checklistId") String checklistId,
+ @Path("itemId") String itemId
+ );
+
+ /**
+ * Change the title of the checklist item.
+ *
+ * @param boardId The ID of the board
+ * @param cardId The ID of the card
+ * @param checklistId The ID of the checklist
+ * @param itemId The ID of the item
+ * @param title The new title
+ * @return The ID of the checklist item
+ */
+ @FormUrlEncoded
+ @PUT("/api/boards/{boardId}/cards/{cardId}/checklists/{checklistId}/items/{itemId}")
+ Call editChecklistItem(
+ @Path("boardId") String boardId,
+ @Path("cardId") String cardId,
+ @Path("checklistId") String checklistId,
+ @Path("itemId") String itemId,
+ @Field("title") String title
+ );
+
+ /**
+ * Change the state of the checklist item.
+ *
+ * @param boardId The ID of the board
+ * @param cardId The ID of the card
+ * @param checklistId The ID of the checklist
+ * @param itemId The ID of the item
+ * @param itemStatus The status of the item
+ * @return The ID of the checklist item
+ * @see ChecklistItemStatus
+ */
+ @PUT("/api/boards/{boardId}/cards/{cardId}/checklists/{checklistId}/items/{itemId}")
+ Call editChecklistItem(
+ @Path("boardId") String boardId,
+ @Path("cardId") String cardId,
+ @Path("checklistId") String checklistId,
+ @Path("itemId") String itemId,
+ @Body ChecklistItemStatus itemStatus
+ );
+
+ /**
+ * Delete a checklist item from the checklist.
+ *
+ * @param boardId The ID of the board
+ * @param cardId The ID of the card
+ * @param checklistId The ID of the checklist
+ * @param itemId The ID of the item
+ * @return The ID of the checklist item
+ */
+ @DELETE("/api/boards/{boardId}/cards/{cardId}/checklists/{checklistId}/items/{itemId}")
+ Call deleteChecklistItem(
+ @Path("boardId") String boardId,
+ @Path("cardId") String cardId,
+ @Path("checklistId") String checklistId,
+ @Path("itemId") String itemId
+ );
+}
diff --git a/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/ListService.java b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/ListService.java
new file mode 100644
index 0000000..4d929a9
--- /dev/null
+++ b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/ListService.java
@@ -0,0 +1,57 @@
+package it.unisannio.ding.ids.wedroid.wrapper.api;
+
+import it.unisannio.ding.ids.wedroid.wrapper.entity.WList;
+import java.util.List;
+import retrofit2.Call;
+import retrofit2.http.DELETE;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.Path;
+
+public interface ListService {
+
+ /**
+ * Get the list of Lists attached to a board
+ *
+ * @param boardId the ID of the board
+ * @return the list of all lists
+ */
+ @GET("/api/boards/{board}/lists")
+ Call> getAllList(@Path("board") String boardId);
+
+ /**
+ * Add a List to a board
+ *
+ * @param boardId the ID of the string
+ * @param title title of the new list
+ * @return the new list
+ */
+ @FormUrlEncoded
+ @POST("/api/boards/{board}/lists")
+ Call newList(
+ @Path("board") String boardId,
+ @Field("title") String title
+ );
+
+ /**
+ * Get a List attached to a board
+ * @param boardId the ID of the board
+ * @param listId the ID of the list
+ * @return the list
+ */
+ @GET("/api/boards/{board}/lists/{list}")
+ Call getList(@Path("board") String boardId, @Path("list") String listId);
+
+ /**
+ * Delete a List
+ * @param boardId the ID of the board
+ * @param listId the ID of the list
+ * @return ID of the delete list
+ */
+ @DELETE("/api/boards/{board}/lists/{list}")
+ Call deleteList(@Path("board") String boardId, @Path("list") String listId);
+
+
+}
diff --git a/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/LoginService.java b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/LoginService.java
new file mode 100644
index 0000000..925410b
--- /dev/null
+++ b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/LoginService.java
@@ -0,0 +1,20 @@
+package it.unisannio.ding.ids.wedroid.wrapper.api;
+
+import it.unisannio.ding.ids.wedroid.wrapper.entity.UserPrototype;
+import retrofit2.Call;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.POST;
+
+public interface LoginService {
+
+ /**
+ *
+ * @param username
+ * @param password
+ * @return User id and token
+ */
+ @FormUrlEncoded
+ @POST("/users/login")
+ Call login(@Field("username") String username, @Field("password") String password);
+}
diff --git a/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/SwimlanesService.java b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/SwimlanesService.java
new file mode 100644
index 0000000..aaa9746
--- /dev/null
+++ b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/SwimlanesService.java
@@ -0,0 +1,52 @@
+package it.unisannio.ding.ids.wedroid.wrapper.api;
+
+import it.unisannio.ding.ids.wedroid.wrapper.entity.Swimlane;
+import java.util.List;
+import retrofit2.Call;
+import retrofit2.http.DELETE;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.Path;
+
+public interface SwimlanesService {
+ /**
+ * Get the list of swimlanes attached to a board.
+ *
+ * @param boardId The ID of the board
+ * @return The list of swimlanes
+ */
+ @GET("/api/boards/{boardId}/swimlanes")
+ Call> getAllSwimlanes(@Path("boardId") String boardId);
+
+ /**
+ * Add a swimlane to a board.
+ *
+ * @param boardId The ID of the board
+ * @param swimlaneTitle The new title of the swimlane
+ * @return The new swimlane
+ */
+ @FormUrlEncoded
+ @POST("/api/boards/{boardId}/swimlanes")
+ Call newSwimlane(@Path("boardId") String boardId, @Field("title") String swimlaneTitle);
+
+ /**
+ * Get a swimlane.
+ *
+ * @param boardId The ID of the board
+ * @param swimlaneId The ID of the swimlane
+ * @return The swimlane
+ */
+ @GET("/api/boards/{boardId}/swimlanes/{swimlaneId}")
+ Call getSwimlane(@Path("boardId") String boardId, @Path("swimlaneId") String swimlaneId);
+
+ /**
+ * Delete a swimlane.
+ *
+ * @param boardId The ID of the board
+ * @param swimlaneId The ID of the swimlane
+ */
+ @DELETE("/api/boards/{boardId}/swimlanes/{swimlaneId}")
+ Call deleteSwimlane(@Path("boardId") String boardId, @Path("swimlaneId") String swimlaneId);
+}
diff --git a/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/UserService.java b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/UserService.java
new file mode 100644
index 0000000..2d61f47
--- /dev/null
+++ b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/api/UserService.java
@@ -0,0 +1,78 @@
+package it.unisannio.ding.ids.wedroid.wrapper.api;
+
+import it.unisannio.ding.ids.wedroid.wrapper.entity.Action;
+import it.unisannio.ding.ids.wedroid.wrapper.entity.Board;
+import it.unisannio.ding.ids.wedroid.wrapper.entity.User;
+import java.util.List;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.DELETE;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.Headers;
+import retrofit2.http.POST;
+import retrofit2.http.Path;
+
+public interface UserService {
+
+ /**
+ * Get all users.
+ *
+ * @return the list with all users
+ */
+ @GET("api/users")
+ Call> getAllUser();
+
+ /**
+ * Get user information
+ *
+ * @param userId ID user
+ * @return user information
+ */
+ @GET("api/users/{user}")
+ Call getUser(@Path("user") String userId);
+
+ /**
+ * Get current User
+ * @return the current user
+ */
+ @GET("api/user")
+ Call getCurrentUser();
+
+
+ /**
+ * Delete user
+ * @param userId
+ * @return
+ */
+ @DELETE("api/users/{user}")
+ Call delete(@Path("user") String userId);
+
+
+ /******************** Don't work ****************************************************/
+
+ @FormUrlEncoded
+ @POST("api/users")
+ @Headers("Content-Type: multipart/form-data")
+ Call newUser(@Field("username") String username,
+ @Field("email") String email,
+ @Field("password") String password
+ );
+
+
+
+ @FormUrlEncoded
+ @Headers("Content-Type: multipart/form-data")
+ @POST("/api/boards/{board}/members/{user}/add")
+ Call addMemberToBoard(@Path("board") String boardId, @Path("user") String userId,
+ @Field("action") String action,
+ @Field("isAdmin") boolean b1,
+ @Field("isNoComments") boolean b2,
+ @Field("isCommentOnly") boolean b3);
+
+ @POST("api/boards/{board}/members/{user}/remove")
+ Call removeUserFromBoard(@Path("board") String boardId, @Path("user") String userId,
+ @Body Action action);
+
+}
diff --git a/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/entity/Action.java b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/entity/Action.java
new file mode 100644
index 0000000..3fb4a17
--- /dev/null
+++ b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/entity/Action.java
@@ -0,0 +1,13 @@
+package it.unisannio.ding.ids.wedroid.wrapper.entity;
+
+import com.google.gson.annotations.SerializedName;
+
+public enum Action {
+
+ @SerializedName("takeOwnership")
+ TAKE_OWNERSHIP,
+ @SerializedName("disableLogin")
+ DISABLE_LOGIN,
+ @SerializedName("enableLogin")
+ ENABLE_LOGIN
+}
diff --git a/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/entity/Board.java b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/entity/Board.java
new file mode 100644
index 0000000..9736f30
--- /dev/null
+++ b/wrapper/src/main/java/it/unisannio/ding/ids/wedroid/wrapper/entity/Board.java
@@ -0,0 +1,155 @@
+package it.unisannio.ding.ids.wedroid.wrapper.entity;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.Date;
+import java.util.List;
+
+public class Board {
+ @SerializedName("_id")
+ private String id;
+ private String title;
+ private String slug;
+ private boolean archived;
+ private Date createdAt;
+ private Date modifiedAt;
+ private int starts;
+ private List