Merge branch 'release/0.1' of kanban/wedroid into master
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details

This commit is contained in:
noemi3 2020-01-18 14:58:59 +00:00 committed by Gitea
commit 5d1571f5ff
129 changed files with 9087 additions and 6 deletions

110
.drone.yml Normal file
View File

@ -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

180
.gitignore vendored Normal file
View File

@ -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

View File

@ -0,0 +1,125 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<AndroidXmlCodeStyleSettings>
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -0,0 +1,6 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View File

@ -0,0 +1,6 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="Copyright (c) &amp;#36;today.year, norangebit &#10;&#10;This file is part of augmented-images.&#10;&#10; &amp;#36;project.name is free software: you can redistribute it and/or modify&#10; it under the terms of the GNU General Public License as published by&#10; the Free Software Foundation, either version 3 of the License, or&#10; (at your option) any later version.&#10;&#10; &amp;#36;project.name is distributed in the hope that it will be useful,&#10; but WITHOUT ANY WARRANTY; without even the implied warranty of&#10; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&#10; GNU General Public License for more details.&#10;&#10; You should have received a copy of the GNU General Public License&#10; along with &amp;#36;project.name. If not, see &lt;http://www.gnu.org/licenses/&gt;&#10;" />
<option name="myName" value="GPL v. 2.0" />
</copyright>
</component>

14
.idea/misc.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeSettings">
<configurations>
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
</configurations>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@ -6,10 +6,8 @@
Client Android per Wekan sviluppato per il progetto del corso magistrale Ingegneria del Software per l'Università del Sannio. 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

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

78
app/build.gradle Normal file
View File

@ -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"
}

21
app/proguard-rules.pro vendored Normal file
View File

@ -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

View File

@ -0,0 +1,22 @@
package it.unisannio.ding.ids.wedroid.app
import androidx.lifecycle.*
class OneTimeObserver<T>(private val handler: (T) -> Unit) : Observer<T>, 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 <T> LiveData<T>.observeOnce(onChangeHandler: (T) -> Unit) {
val observer = OneTimeObserver(handler = onChangeHandler)
observe(observer, observer)
}

View File

@ -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<Context>()
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)
}
}
}

View File

@ -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<Context>()
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)
}
}
}

View File

@ -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<BoardsListAdapter.BoardViewHolder>(
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()))
}
}

View File

@ -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()))
}
}

View File

@ -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()))
}
}

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="it.unisannio.ding.ids.wedroid.app">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity android:name=".view.LoginActivity" />
<activity
android:name=".view.NewBoardActivity"
android:label="@string/title_activity_new_board"
android:theme="@style/AppTheme.NoActionBar"/>
<activity
android:name=".view.BoardViewActivity"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".view.WListsListActivity"
android:label="WList"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".view.BoardsListsActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -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<List<Board>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(board: Board)
@Delete
suspend fun delete(board: Board)
}

View File

@ -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<List<WList>> getAllWList();
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(WList wList);
@Delete
void delete(WList wList);
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>> {
override fun onFailure(
call: Call<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>,
t: Throwable
) = logNetworkError(t.message)
override fun onResponse(
call: Call<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>,
response: Response<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>
) {
CoroutineScope(Dispatchers.IO).launch {
synchronizeCallback(response)
}
}
})
}
private suspend fun synchronizeCallback(
response: Response<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>
) {
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<it.unisannio.ding.ids.wedroid.wrapper.entity.Board> {
override fun onFailure(
call: Call<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>,
t: Throwable
) = logNetworkError(t.message)
override fun onResponse(
call: Call<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>,
response: Response<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>
) {
CoroutineScope(Dispatchers.IO).launch {
insertBoardCallback(response, title)
}
}
})
}
private suspend fun insertBoardCallback(
response: Response<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>,
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<Void> {
override fun onFailure(call: Call<Void>, t: Throwable) {
logNetworkError(t.message)
}
override fun onResponse(
call: Call<Void>,
response: Response<Void>
) {
CoroutineScope(Dispatchers.IO).launch {
deleteBoardCallback(response, id)
}
}
})
}
private suspend fun deleteBoardCallback(
response: Response<Void>,
id: String
) {
if (!response.isSuccessful) {
logNetworkError("${response.code()} ${response.message()}")
return
}
dao.delete(Board(id))
}
private suspend fun addNewBoardToDb(boards: Collection<Board>) {
boards.forEach {
dao.insert(it)
}
}
private suspend fun removeOldBoardsFromDb(boards: Collection<Board>) {
allBoards.value?.minus(boards)
?.forEach {
dao.delete(it)
}
}
private fun logNetworkError(message: String?) {
Log.e("RETROFIT", message)
}
}

View File

@ -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<MutableList<WList>> by lazy {
dao.allWList
}
init {
synchronize()
}
fun synchronize() {
service.getAllList(reader.boardId)
.enqueue(object :
Callback<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.WList>> {
override fun onFailure(
call: Call<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.WList>>,
t: Throwable
) = logNetworkError(t.message)
override fun onResponse(
call: Call<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.WList>>,
response: Response<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.WList>>
) {
CoroutineScope(Dispatchers.IO).launch {
synchronizeCallback(response)
}
}
})
}
private fun synchronizeCallback(
response: Response<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.WList>>
) {
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<it.unisannio.ding.ids.wedroid.wrapper.entity.WList> {
override fun onFailure(
call: Call<it.unisannio.ding.ids.wedroid.wrapper.entity.WList>,
t: Throwable
) {
logNetworkError(t.message)
}
override fun onResponse(
call: Call<it.unisannio.ding.ids.wedroid.wrapper.entity.WList>,
response: Response<it.unisannio.ding.ids.wedroid.wrapper.entity.WList>
) {
CoroutineScope(Dispatchers.IO).launch {
deleteWListCallBack(response, idWList)
}
}
})
}
private fun deleteWListCallBack(
response: Response<it.unisannio.ding.ids.wedroid.wrapper.entity.WList>,
id: String
) {
if (!response.isSuccessful) {
logNetworkError("${response.code()} ${response.message()}")
return
}
dao.delete(WList(id))
}
private fun addNewWListToDb(wLists: Collection<WList>) {
wLists.forEach {
dao.insert(it)
}
}
private fun removeOldWListsFromDb(wLists: Collection<WList>) {
allWLists.value?.minus(wLists)
?.forEach {
dao.delete(it)
}
}
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
private fun logNetworkError(message: String?) {
Log.e("RETROFIT", message)
}
}

View File

@ -0,0 +1,11 @@
package it.unisannio.ding.ids.wedroid.app.util;
public interface PreferenceReader {
String getBaseUrl();
String getUserId();
String getToken();
String getBoardId();
}

View File

@ -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);
}

View File

@ -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
}
}
}
}

View File

@ -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()
}
}

View File

@ -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<Board>() {
@Override
public void onResponse(@NotNull Call<Board> call, @NotNull Response<Board> 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<String> labelsTitle = new ArrayList<>();
for (int i = 0; i < board.getLabels().size(); i++) {
labelsTitle.add(board.getLabels().get(i).getName());
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(getApplicationContext(),
android.R.layout.simple_list_item_1, labelsTitle);
listView.setAdapter(adapter);
getListsButton.setEnabled(true);
}
}
@Override
public void onFailure(@NotNull Call<Board> 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<User>() {
@Override
public void onResponse(@NotNull Call<User> call, @NotNull Response<User> 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<User> 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();
}
}
}
}

View File

@ -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
}
}

View File

@ -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<UserPrototype> {
override fun onFailure(call: Call<UserPrototype>, t: Throwable) {
Toast.makeText(
applicationContext,
R.string.login_network_error,
Toast.LENGTH_LONG
).show()
}
override fun onResponse(call: Call<UserPrototype>, response: Response<UserPrototype>) {
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
}
}

View File

@ -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<CharSequence> 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";
}

View File

@ -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<List<WList>>() {
@Override
public void onChanged(List<WList> 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);
}
}

View File

@ -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<BoardsListAdapter.BoardViewHolder>() {
private val inflater = LayoutInflater.from(context)
private var boards = emptyList<Board>()
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<Board>) {
this.boards = boards
notifyDataSetChanged()
}
companion object {
const val BOARD_ID = "BOARD_ID"
}
}

View File

@ -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<WListsAdapter.WListViewHolder> {
private final LayoutInflater mInflater;
private List<WList> 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<String> cardTitle = new ArrayList<>();
service.getCardService().getAllCards(sp.getBoardId(), current.getId()).enqueue(
new Callback<List<Card>>() {
@Override
public void onResponse(@NotNull Call<List<Card>> call, @NotNull Response<List<Card>> response) {
assert response.body() != null;
for (int i = 0; i < response.body().size(); i++) {
cardTitle.add(response.body().get(i).getTitle());
}
ArrayAdapter<String> 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<List<Card>> 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> 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<List<Swimlane>>() {
@Override
public void onResponse(@NotNull Call<List<Swimlane>> call,
@NotNull Response<List<Swimlane>> 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<it.unisannio.ding.ids.wedroid.wrapper.entity.WList>() {
@Override
public void onResponse(@NotNull Call<it.unisannio.ding.ids.wedroid.wrapper.entity.WList> call, @NotNull Response<it.unisannio.ding.ids.wedroid.wrapper.entity.WList> response) {
service.getCardService().newCard(sp.getBoardId(), current, card).enqueue(new Callback<Card>() {
@Override
public void onResponse(@NotNull Call<Card> call, @NotNull Response<Card> 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<Card> call, @NotNull Throwable t) {
}
});
}
@Override
public void onFailure(@NotNull Call<it.unisannio.ding.ids.wedroid.wrapper.entity.WList> call, @NotNull Throwable t) {
}
});
}
@Override
public void onFailure(@NotNull Call<List<Swimlane>> 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();
}
}

View File

@ -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<List<Board>> 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<List<Board>> getAllBoards() {
return allBoards;
}
public void insertBoard(String title, boolean isPrivate, BoardBackgroundColor color) {
repository.insertBoard(title, isPrivate, color);
}
public void deleteBoard(int position) {
List<Board> boards = allBoards.getValue();
if (boards != null)
repository.deleteBoard(boards.get(position).getId());
}
public void refresh() {
repository.synchronize();
}
}

View File

@ -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<List<WList>> 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<List<WList>> getAllWLists(){
return allWLists;
}
public void deleteWList(int position, String idBoard) {
List<WList> wList = allWLists.getValue();
if (wList != null)
wListRepository.deleteWList(idBoard, wList.get(position).getId());
}
public void refresh() {
wListRepository.synchronize();
}
}

View File

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<shape android:shape="rectangle">
<solid android:color="@color/colorAccent"></solid>
<corners android:radius="11dp"></corners>
</shape>
</selector>

View File

@ -0,0 +1,212 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/pullToRefresh"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollIndicators="right"
android:scrollbarStyle="insideOverlay"
android:scrollbars="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/my_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/textViewInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/padding"
android:paddingRight="@dimen/padding"
android:paddingTop="1dp"
android:text="@string/info_board"
android:textAlignment="center"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/padding"
android:paddingRight="@dimen/padding"
android:text="@string/description"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/descriptionTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/padding"
android:paddingRight="@dimen/padding"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:text="@string/defaultTxt" />
<View
android:id="@+id/divider1"
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="#FF008577"
android:visibility="visible" />
<TextView
android:id="@+id/members"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/padding"
android:paddingRight="@dimen/padding"
android:paddingTop="3dp"
android:text="@string/members"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/membersTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:text="@string/defaultTxt" />
<View
android:id="@+id/divider2"
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="#FF008577"
android:visibility="visible" />
<TextView
android:id="@+id/permission"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/padding"
android:paddingRight="@dimen/padding"
android:paddingTop="3dp"
android:text="@string/permission"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/permissionTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/padding"
android:paddingRight="@dimen/padding"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:text="@string/defaultTxt" />
<View
android:id="@+id/divider3"
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="#FF008577"
android:visibility="visible" />
<TextView
android:id="@+id/labels"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/padding"
android:paddingRight="@dimen/padding"
android:paddingTop="3dp"
android:text="@string/labels"
android:textSize="18sp"
android:textStyle="bold" />
<ListView
android:id="@+id/listViewID"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="3dp"
android:layout_marginRight="8dp"
android:nestedScrollingEnabled="true" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.button.MaterialButton
android:id="@+id/getLists"
style="@style/AppTheme.RoundedCornerMaterialButton"
android:layout_width="271dp"
android:layout_height="wrap_content"
android:layout_marginStart="70dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="70dp"
android:stateListAnimator="@android:anim/fade_in"
android:text="@string/view_lists_button_name"
android:textAlignment="center"
app:backgroundTint="#80CBC4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/modifiedDate"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="50dp"
android:text="@string/modified_at"
android:textAlignment="textEnd"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/getLists"
app:layout_constraintVertical_bias="0.0" />
<TextView
android:id="@+id/createdDate"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="30dp"
android:layout_marginBottom="50dp"
android:text="@string/created_at"
android:textAlignment="viewStart"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/modifiedDate"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/getLists"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.BoardsListsActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_boards_lists" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@drawable/ic_add_black_24dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.LoginActivity">
<EditText
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="50dp"
android:ems="10"
android:hint="UserName"
android:inputType="textPersonName"
app:layout_constraintBottom_toTopOf="@+id/password"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<EditText
android:id="@+id/password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="100dp"
android:ems="10"
android:hint="Password"
android:inputType="textPassword"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="200dp"
android:onClick="loginButton"
android:text="LOGIN"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<EditText
android:id="@+id/instanceServer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="50dp"
android:ems="10"
android:hint="Istance Server"
android:inputType="textPersonName"
app:layout_constraintBottom_toTopOf="@+id/username"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.NewBoardActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_new_board" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@id/my_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerviewWList"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/my_toolbar"
tools:listitem="@layout/wlist_recyclerview_item" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/synchronize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:srcCompat="@android:drawable/ic_popup_sync" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/enter_card_name"
android:id="@+id/textView" />
<EditText
android:id="@+id/edittext"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="@string/enter_text_here"
android:padding="10dp" />
</LinearLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/boardTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/board_item_padding"
android:textSize="24sp" />
</LinearLayout>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".view.BoardsListsActivity"
tools:showIn="@layout/activity_boards_lists">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:id="@+id/pullToRefresh"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="0dp"
android:layout_height="0dp"
android:id="@+id/boardList"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".view.NewBoardActivity"
tools:showIn="@layout/activity_new_board">
<EditText
android:id="@+id/newBoardName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint="@string/new_board_name_field"
android:importantForAutofill="no"
android:inputType="textPersonName"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.20999998" />
<Switch
android:id="@+id/newBoardPermission"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/new_board_switch"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/newBoardName"
app:layout_constraintStart_toStartOf="@+id/newBoardName"
app:layout_constraintTop_toBottomOf="@+id/newBoardName"
app:layout_constraintVertical_bias="0.1" />
<Button
android:id="@+id/newBoardDone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onDone"
android:text="@string/new_board_done_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/newBoardPermission"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/newBoardColor"
app:layout_constraintVertical_bias="0.40" />
<Button
android:id="@+id/newBoardCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onCancel"
android:text="@string/new_board_cancel_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/newBoardPermission"
app:layout_constraintTop_toTopOf="@+id/newBoardColor"
app:layout_constraintVertical_bias="0.40" />
<Spinner
android:id="@+id/newBoardColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/newBoardPermission"
app:layout_constraintStart_toStartOf="@+id/newBoardPermission"
app:layout_constraintTop_toBottomOf="@+id/newBoardPermission"
app:layout_constraintVertical_bias="0.120000005" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/wListTitle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#BBDEFB"
android:orientation="horizontal"
android:textAlignment="center"
android:textSize="24sp"
android:textStyle="bold" />
<ListView
android:id="@+id/listViewCard"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/buttonAddCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/add_card_button" />
</LinearLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>

View File

@ -0,0 +1,8 @@
<resources>
<dimen name="fab_margin">16dp</dimen>
<dimen name="padding">10dp</dimen>
<dimen name="board_item_padding">8dp</dimen>
</resources>

View File

@ -0,0 +1,46 @@
<resources>
<string name="app_name">wedroid</string>
<string name="view_lists_button_name">View lists</string>
<string name="info_board">Info board</string>
<string name="description">-Description:</string>
<string name="defaultTxt">***</string>
<string name="members">-Members:</string>
<string name="permission">-Permission:</string>
<string name="labels">-Labels:</string>
<string name="modified_at">Modified at:</string>
<string name="created_at">Created at:</string>
<string name="add_card_button">Add Card</string>
<string name="enter_text_here">Enter text here...</string>
<string name="enter_card_name">Enter card name</string>
<string name="title_activity_boards_lists">BoardsListActivity</string>
<string name="title_activity_new_board">NewBoardActivity</string>
<string name="new_board_name_field">Board name</string>
<string name="new_board_switch">Private</string>
<string name="new_board_cancel_button">Cancel</string>
<string name="new_board_done_button">Done</string>
<string name="on_null_new_board_name">There was a problem with the name of the new board</string>
<string name="on_add_new_board_error">It was not possible to add a new board</string>
<string name="on_add_new_board_empty_name">Name cannot be empty</string>
<string name="board_deleted">The selected board has been deleted</string>
<string-array name="board_background_colors">
<item>Belize</item>
<item>Nephritis</item>
<item>Pomegranate</item>
<item>Pumpkin</item>
<item>Wisteria</item>
<item>Moderatepink</item>
<item>Strongcyan</item>
<item>Limegreen</item>
<item>Midnight</item>
<item>Dark</item>
<item>Relax</item>
<item>Corteza</item>
</string-array>
<string name="login_empty_field">Riempire tutti i campi</string>
<string name="login_unformed_instance">Formato URL non valido</string>
<string name="login_network_error">Controlla la tua connessione internet</string>
<string name="login_wrong_field">Credenziali non corrette</string>
<string name="login_success">Login effettuato con successo</string>
</resources>

View File

@ -0,0 +1,24 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
<style name="AppTheme.RoundedCornerMaterialButton" parent="Widget.AppCompat.Button.Colored">
<item name="android:background">@drawable/rounded_shape</item>
</style>
</resources>

View File

@ -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 }
}

View File

@ -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<PreferenceReader>()
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<BoardDao>()
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<BoardDao>()
val response =
mockk<Response<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>()
val board = mockk<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>()
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<BoardDao>()
val response =
mockk<Response<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>()
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<BoardDao>()
val response =
mockk<Response<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>()
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<BoardDao>()
val response =
mockk<Response<Void>>()
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<BoardDao>()
val response =
mockk<Response<Void>>()
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<BoardDao>()
val dbBoards = mockk<LiveData<List<Board>>>()
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<BoardDao>()
val response =
mockk<Response<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>>()
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<BoardDao>()
val response =
mockk<Response<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.Board>>>()
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
)
}
}
}

View File

@ -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<PreferenceReader>()
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<WListDao>()
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<WListDao>()
val response =
mockk<Response<Void>>()
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<WListDao>()
val response = mockk<Response<Void>>()
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<WListDao>()
val dbWLists = mockk<LiveData<List<WList>>>()
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<WListDao>()
val response =
mockk<Response<MutableList<it.unisannio.ding.ids.wedroid.wrapper.entity.WList>>>()
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") }
}
}

36
build.gradle Normal file
View File

@ -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
}

View File

@ -0,0 +1,239 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
Checkstyle configuration that checks the Google coding conventions from Google Java Style
that can be found at https://google.github.io/styleguide/javaguide.html
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.sf.net (or in your downloaded distribution).
To completely disable a check, just comment it out or delete it from the file.
Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
-->
<module name = "Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="warning"/>
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Excludes all 'module-info.java' files -->
<!-- See https://checkstyle.org/config_filefilters.html -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<module name="TreeWalker">
<module name="OuterTypeFilename"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
<property name="format"
value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message"
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
</module>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="AvoidStarImport"/>
<module name="OneTopLevelClass"/>
<module name="NoLineWrap"/>
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="LeftCurly"/>
<module name="RightCurly">
<property name="id" value="RightCurlySame"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE,
LITERAL_DO"/>
</module>
<module name="RightCurly">
<property name="id" value="RightCurlyAlone"/>
<property name="option" value="alone"/>
<property name="tokens"
value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
INSTANCE_INIT"/>
</module>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyLambdas" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLoops" value="true"/>
<message key="ws.notFollowed"
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
<message key="ws.notPreceded"
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
</module>
<module name="OneStatementPerLine"/>
<module name="MultipleVariableDeclarations"/>
<module name="ArrayTypeStyle"/>
<module name="MissingSwitchDefault"/>
<module name="FallThrough"/>
<module name="UpperEll"/>
<module name="ModifierOrder"/>
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapDot"/>
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapComma"/>
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/258 -->
<property name="id" value="SeparatorWrapEllipsis"/>
<property name="tokens" value="ELLIPSIS"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/259 -->
<property name="id" value="SeparatorWrapArrayDeclarator"/>
<property name="tokens" value="ARRAY_DECLARATOR"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapMethodRef"/>
<property name="tokens" value="METHOD_REF"/>
<property name="option" value="nl"/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern"
value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="TypeName">
<message key="name.invalidPattern"
value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MemberName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LambdaParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="CatchParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalVariableName">
<property name="tokens" value="VARIABLE_DEF"/>
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ClassTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="InterfaceTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Interface type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="NoFinalizer"/>
<module name="GenericWhitespace">
<message key="ws.followed"
value="GenericWhitespace ''{0}'' is followed by whitespace."/>
<message key="ws.preceded"
value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
<message key="ws.illegalFollow"
value="GenericWhitespace ''{0}'' should followed by whitespace."/>
<message key="ws.notPreceded"
value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
</module>
<module name="Indentation">
<property name="basicOffset" value="4"/>
<property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="4"/>
<property name="throwsIndent" value="4"/>
<property name="lineWrappingIndentation" value="4"/>
<property name="arrayInitIndent" value="4"/>
</module>
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="false"/>
<property name="allowedAbbreviationLength" value="1"/>
</module>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="CustomImportOrder">
<property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" value="true"/>
<property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
</module>
<module name="MethodParamPad"/>
<module name="NoWhitespaceBefore">
<property name="tokens"
value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS, METHOD_REF"/>
<property name="allowLineBreaks" value="true"/>
</module>
<module name="ParenPad"/>
<module name="OperatorWrap">
<property name="option" value="eol"/>
<property name="tokens"
value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,
LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF "/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationMostCases"/>
<property name="tokens"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationVariables"/>
<property name="tokens" value="VARIABLE_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
<property name="target"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
<message key="name.invalidPattern"
value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="SingleLineJavadoc">
<property name="ignoreInlineTags" value="false"/>
</module>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected"/>
</module>
<module name="CommentsIndentation"/>
</module>
</module>

580
config/detekt/detekt.yml Normal file
View File

@ -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.*'

21
gradle.properties Normal file
View File

@ -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

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -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

172
gradlew vendored Executable file
View File

@ -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" "$@"

84
gradlew.bat vendored Normal file
View File

@ -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

2
settings.gradle Normal file
View File

@ -0,0 +1,2 @@
include ':app', ':wrapper'
rootProject.name='wedroid'

32
wrapper/build.gradle.kts Normal file
View File

@ -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<Jar>("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")
}
}
}

View File

@ -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<List<Board>> getPublicBoards();
/**
* Create a board.
*
* @param boardPrototype the prototype of the new board
* @return the new board
* @see BoardPrototype
*/
@POST("api/boards")
Call<Board> 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<Board> getBoard(@Path("boardId") String boardId);
/**
* Delete the board with that particular ID
*
* @param boardId The ID of the board
*/
@DELETE("api/boards/{boardId}")
Call<Void> 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<String> 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<Void> 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<List<Board>> getBoardsFromUser(@Path("userId") String userId);
}

View File

@ -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<List<Comment>> 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<Comment> 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<Comment> 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<Void> deleteComment(
@Path("boardId") String boardId,
@Path("cardId") String cardId,
@Path("commentId") String commentId
);
}

View File

@ -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<List<Card>> 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<Void> 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<Card> 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<Card> 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<List<Card>> 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<Card> 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<Card> moveCard(
@Path("board") String boardID,
@Path("list") String oldListID,
@Path("card") String cardID,
@Field("listId") String newListId
);
}

View File

@ -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<List<Checklist>> 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<Checklist> 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<Checklist> 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<Checklist> 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<Checklist> 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<ChecklistItem> 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<ChecklistItem> 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<ChecklistItem> 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<ChecklistItem> deleteChecklistItem(
@Path("boardId") String boardId,
@Path("cardId") String cardId,
@Path("checklistId") String checklistId,
@Path("itemId") String itemId
);
}

View File

@ -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<List<WList>> 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<WList> 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<WList> 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<WList> deleteList(@Path("board") String boardId, @Path("list") String listId);
}

View File

@ -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<UserPrototype> login(@Field("username") String username, @Field("password") String password);
}

View File

@ -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<List<Swimlane>> 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<Swimlane> 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<Swimlane> 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<Void> deleteSwimlane(@Path("boardId") String boardId, @Path("swimlaneId") String swimlaneId);
}

View File

@ -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<List<User>> getAllUser();
/**
* Get user information
*
* @param userId ID user
* @return user information
*/
@GET("api/users/{user}")
Call<User> getUser(@Path("user") String userId);
/**
* Get current User
* @return the current user
*/
@GET("api/user")
Call<User> getCurrentUser();
/**
* Delete user
* @param userId
* @return
*/
@DELETE("api/users/{user}")
Call<User> delete(@Path("user") String userId);
/******************** Don't work ****************************************************/
@FormUrlEncoded
@POST("api/users")
@Headers("Content-Type: multipart/form-data")
Call<User> 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<Board> 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<Void> removeUserFromBoard(@Path("board") String boardId, @Path("user") String userId,
@Body Action action);
}

View File

@ -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
}

View File

@ -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<Label> labels;
private List<Member> members;
private BoardPermission permission;
@SerializedName("color")
private BoardBackgroundColor backgroundColor;
private String description;
private String subtasksDefaultBoardId;
private String subtasksDefaultListId;
private boolean allowsSubtasks;
private PresentParentTask presentParentTask;
private Date startAt;
private Date dueAt;
private Date endAt;
private int spentTime;
private boolean isOvertime;
private String type;
private String defaultSwimlaneId;
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public String getSlug() {
return slug;
}
public boolean isArchived() {
return archived;
}
public Date getCreatedAt() {
return createdAt;
}
public Date getModifiedAt() {
return modifiedAt;
}
public int getStarts() {
return starts;
}
public List<Label> getLabels() {
return labels;
}
public List<Member> getMembers() {
return members;
}
public BoardPermission getPermission() {
return permission;
}
public BoardBackgroundColor getBackgroundColor() {
return backgroundColor;
}
public String getDescription() {
return description;
}
public String getSubtasksDefaultBoardId() {
return subtasksDefaultBoardId;
}
public String getSubtasksDefaultListId() {
return subtasksDefaultListId;
}
public boolean isAllowsSubtasks() {
return allowsSubtasks;
}
public PresentParentTask getPresentParentTask() {
return presentParentTask;
}
public Date getStartAt() {
return startAt;
}
public Date getDueAt() {
return dueAt;
}
public Date getEndAt() {
return endAt;
}
public int getSpentTime() {
return spentTime;
}
public boolean isOvertime() {
return isOvertime;
}
public String getType() {
return type;
}
public String getDefaultSwimlaneId() {
return defaultSwimlaneId;
}
@Override
public String toString() {
return "Board{" +
"id='" + id + '\'' +
", title='" + title + '\'' +
", slug='" + slug + '\'' +
", archived=" + archived +
", createdAt=" + createdAt +
", modifiedAt=" + modifiedAt +
", starts=" + starts +
", labels=" + labels +
", members=" + members +
", permission=" + permission +
", backgroundColor=" + backgroundColor +
", description='" + description + '\'' +
", subtasksDefaultBoardId='" + subtasksDefaultBoardId + '\'' +
", subtasksDefaultListId='" + subtasksDefaultListId + '\'' +
", allowsSubtasks=" + allowsSubtasks +
", presentParentTask=" + presentParentTask +
", startAt=" + startAt +
", dueAt=" + dueAt +
", endAt=" + endAt +
", spentTime=" + spentTime +
", isOvertime=" + isOvertime +
", type='" + type + '\'' +
", defaultSwimlaneId='" + defaultSwimlaneId + '\'' +
'}';
}
}

View File

@ -0,0 +1,30 @@
package it.unisannio.ding.ids.wedroid.wrapper.entity;
import com.google.gson.annotations.SerializedName;
public enum BoardBackgroundColor {
@SerializedName("belize")
BELIZE,
@SerializedName("nephritis")
NEPHRITIS,
@SerializedName("pomegranate")
POMEGRANATE,
@SerializedName("pumpkin")
PUMPKIN,
@SerializedName("wisteria")
WISTERIA,
@SerializedName("moderatepink")
MODERATEPINK,
@SerializedName("strongcyan")
STRONGCYAN,
@SerializedName("limegreen")
LIMEGREEN,
@SerializedName("midnight")
MIDNIGHT,
@SerializedName("dark")
DARK,
@SerializedName("relax")
RELAX,
@SerializedName("corteza")
CORTEZA
}

View File

@ -0,0 +1,10 @@
package it.unisannio.ding.ids.wedroid.wrapper.entity;
import com.google.gson.annotations.SerializedName;
public enum BoardPermission {
@SerializedName("public")
PUBLIC,
@SerializedName("private")
PRIVATE
}

View File

@ -0,0 +1,133 @@
package it.unisannio.ding.ids.wedroid.wrapper.entity;
import com.google.gson.annotations.SerializedName;
/**
* Describes the properties of the new board
*/
public class BoardPrototype {
private String title;
private String owner;
private boolean isAdmin;
private boolean isActive;
private boolean isNoComments;
private boolean isCommentOnly;
private BoardPermission permission;
@SerializedName("color")
private BoardBackgroundColor backgroundColor;
private BoardPrototype(
String title,
String owner,
boolean isAdmin,
boolean isActive,
boolean isNoComments,
boolean isCommentOnly,
BoardPermission permission,
BoardBackgroundColor backgroundColor
) {
this.title = title;
this.owner = owner;
this.isAdmin = isAdmin;
this.isActive = isActive;
this.isNoComments = isNoComments;
this.isCommentOnly = isCommentOnly;
this.permission = permission;
this.backgroundColor = backgroundColor;
}
public static class Builder {
private String title;
private String owner;
private boolean isActive = true;
private MemberPermission memberPermission = MemberPermission.ADMIN;
private BoardPermission boardPermission = BoardPermission.PRIVATE;
private BoardBackgroundColor backgroundColor = BoardBackgroundColor.BELIZE;
public Builder() {
}
/**
* Create a new BoardPrototype
*
* @return The board prototype
*/
public BoardPrototype build() {
return new BoardPrototype(
title,
owner,
memberPermission.isAdmin(),
isActive,
memberPermission.isNoComments(),
memberPermission.isCommentOnly(),
boardPermission,
backgroundColor
);
}
/**
* Set the title of the new board.
*
* @param title The title of the new board
*/
public Builder setTitle(String title) {
this.title = title;
return this;
}
/**
* Set the owner of the new board.
*
* @param owner The owner of the new board
*/
public Builder setOwner(String owner) {
this.owner = owner;
return this;
}
/**
* Set the owner like an active member of the new board.
*
* @param active True by default
*/
public Builder setActive(boolean active) {
isActive = active;
return this;
}
/**
* Set the permission of the owner on the new board.
*
* @param memberPermission ADMIN by default
* @see MemberPermission
*/
public Builder setMemberPermission(MemberPermission memberPermission) {
this.memberPermission = memberPermission;
return this;
}
/**
* Set the visibility of the new board.
*
* @param boardPermission PRIVATE by default
* @see BoardPermission
*/
public Builder setBoardPermission(BoardPermission boardPermission) {
this.boardPermission = boardPermission;
return this;
}
/**
* Set the background color of the new board.
*
* @param backgroundColor BELIZE by default
* @see BoardBackgroundColor
*/
public Builder setBackgroundColor(BoardBackgroundColor backgroundColor) {
this.backgroundColor = backgroundColor;
return this;
}
}
}

View File

@ -0,0 +1,13 @@
package it.unisannio.ding.ids.wedroid.wrapper.entity;
import com.google.gson.annotations.SerializedName;
public enum BoardView {
@SerializedName("board-view-lists")
BOARD_VIEW_LISTS,
@SerializedName("board-view-swimlanes")
BOARD_VIEW_SWIMLANES,
@SerializedName("board-view-cal")
BOARD_VIEW_CAL
}

View File

@ -0,0 +1,330 @@
package it.unisannio.ding.ids.wedroid.wrapper.entity;
import com.google.gson.annotations.SerializedName;
import java.util.Date;
import java.util.List;
public class Card {
//Constructor for add card
public Card(String authorId, String title, String swimlaneId, String description) {
this.authorId = authorId;
this.title = title;
this.swimlaneId = swimlaneId;
this.description = description;
}
public Card() {
}
public String getAuthorId() {
return authorId;
}
public void setAuthorId(String authorId) {
this.authorId = authorId;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Boolean getArchived() {
return archived;
}
public void setArchived(Boolean archived) {
this.archived = archived;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public String getListId() {
return listId;
}
public void setListId(String listId) {
this.listId = listId;
}
public String getSwimlaneId() {
return swimlaneId;
}
public void setSwimlaneId(String swimlaneId) {
this.swimlaneId = swimlaneId;
}
public String getBoardId() {
return boardId;
}
public void setBoardId(String boardId) {
this.boardId = boardId;
}
public String getCoverId() {
return coverId;
}
public void setCoverId(String coverId) {
this.coverId = coverId;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
public Date getModifiedAt() {
return modifiedAt;
}
public void setModifiedAt(Date modifiedAt) {
this.modifiedAt = modifiedAt;
}
public List<String> getCustomFields() {
return customFields;
}
public void setCustomFields(List<String> customFields) {
this.customFields = customFields;
}
public Date getDateLastActivity() {
return dateLastActivity;
}
public void setDateLastActivity(Date dateLastActivity) {
this.dateLastActivity = dateLastActivity;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getRequestedBy() {
return requestedBy;
}
public void setRequestedBy(String requestedBy) {
this.requestedBy = requestedBy;
}
public String getAssignedBy() {
return assignedBy;
}
public void setAssignedBy(String assignedBy) {
this.assignedBy = assignedBy;
}
public List<String> getLabelIds() {
return labelIds;
}
public void setLabelIds(List<String> labelIds) {
this.labelIds = labelIds;
}
public List<String> getMembers() {
return members;
}
public void setMembers(List<String> members) {
this.members = members;
}
public List<String> getAssignees() {
return assignees;
}
public void setAssignees(List<String> assignees) {
this.assignees = assignees;
}
public Date getReceivedAt() {
return receivedAt;
}
public void setReceivedAt(Date receivedAt) {
this.receivedAt = receivedAt;
}
public Date getStartAt() {
return startAt;
}
public void setStartAt(Date startAt) {
this.startAt = startAt;
}
public Date getDueAt() {
return dueAt;
}
public void setDueAt(Date dueAt) {
this.dueAt = dueAt;
}
public Date getEndAt() {
return endAt;
}
public void setEndAt(Date endAt) {
this.endAt = endAt;
}
public int getSpentTime() {
return spentTime;
}
public void setSpentTime(int spentTime) {
this.spentTime = spentTime;
}
public Boolean getOvertime() {
return isOvertime;
}
public void setOvertime(Boolean overtime) {
isOvertime = overtime;
}
public int getSort() {
return sort;
}
public void setSort(int sort) {
this.sort = sort;
}
public int getSubtaskSort() {
return subtaskSort;
}
public void setSubtaskSort(int subtaskSort) {
this.subtaskSort = subtaskSort;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getLinkedId() {
return linkedId;
}
public void setLinkedId(String linkedId) {
this.linkedId = linkedId;
}
@Override
public String toString() {
return "Card{" +
"id='" + id + '\'' +
", title='" + title + '\'' +
", archived=" + archived +
", parentId='" + parentId + '\'' +
", listId='" + listId + '\'' +
", swimlaneId='" + swimlaneId + '\'' +
", boardId='" + boardId + '\'' +
", coverId='" + coverId + '\'' +
", color='" + color + '\'' +
", createdAt=" + createdAt +
", modifiedAt=" + modifiedAt +
", customFields=" + customFields +
", dateLastActivity=" + dateLastActivity +
", description='" + description + '\'' +
", requestedBy='" + requestedBy + '\'' +
", assignedBy='" + assignedBy + '\'' +
", labelIds=" + labelIds +
", members=" + members +
", assignees=" + assignees +
", receivedAt='" + receivedAt + '\'' +
", startAt='" + startAt + '\'' +
", dueAt='" + dueAt + '\'' +
", endAt='" + endAt + '\'' +
", spentTime=" + spentTime +
", isOvertime=" + isOvertime +
", userId='" + authorId + '\'' +
", sort=" + sort +
", subtaskSort=" + subtaskSort +
", type='" + type + '\'' +
", linkedId='" + linkedId + '\'' +
'}';
}
@SerializedName("_id")
private String id;
private String title;
private Boolean archived;
private String parentId;
private String listId;
private String swimlaneId;
private String boardId;
private String coverId;
private Color color;
private Date createdAt;
private Date modifiedAt;
private List<String> customFields;
private Date dateLastActivity;
private String description;
private String requestedBy;
private String assignedBy;
private List<String> labelIds;
private List<String> members;
private List<String> assignees;
private Date receivedAt;
private Date startAt;
private Date dueAt;
private Date endAt;
private int spentTime;
private Boolean isOvertime;
//private String userId;
private int sort;
private int subtaskSort;
private String type;
private String linkedId;
@SerializedName(value = "authorId", alternate = "userId")
private String authorId;
}

View File

@ -0,0 +1,64 @@
package it.unisannio.ding.ids.wedroid.wrapper.entity;
import com.google.gson.annotations.SerializedName;
import java.util.Date;
import java.util.List;
public class Checklist {
@SerializedName("_id")
private String id;
private String cardId;
private String title;
private List<ChecklistItem> items;
private Date finishedAt;
private Date createdAt;
private Date modifiedAt;
private int sort;
public String getId() {
return id;
}
public String getCardId() {
return cardId;
}
public String getTitle() {
return title;
}
public List<ChecklistItem> getItems() {
return items;
}
public Date getFinishedAt() {
return finishedAt;
}
public Date getCreatedAt() {
return createdAt;
}
public Date getModifiedAt() {
return modifiedAt;
}
public int getSort() {
return sort;
}
@Override
public String toString() {
return "Checklist{" +
"id='" + id + '\'' +
", cardId='" + cardId + '\'' +
", title='" + title + '\'' +
", items=" + items +
", finishedAt=" + finishedAt +
", createdAt=" + createdAt +
", modifiedAt=" + modifiedAt +
", sort=" + sort +
'}';
}
}

View File

@ -0,0 +1,70 @@
package it.unisannio.ding.ids.wedroid.wrapper.entity;
import com.google.gson.annotations.SerializedName;
import java.util.Date;
public class ChecklistItem {
@SerializedName("_id")
private String id;
private String title;
private String checklistId;
private String cardId;
private String userId;
private int sort;
private Date createdAt;
private Date modifiedAt;
private boolean isFinished;
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public String getChecklistId() {
return checklistId;
}
public String getCardId() {
return cardId;
}
public String getUserId() {
return userId;
}
public int getSort() {
return sort;
}
public Date getCreatedAt() {
return createdAt;
}
public Date getModifiedAt() {
return modifiedAt;
}
public boolean isFinished() {
return isFinished;
}
@Override
public String toString() {
return "ChecklistItem{" +
"id='" + id + '\'' +
", title='" + title + '\'' +
", checklistId='" + checklistId + '\'' +
", cardId='" + cardId + '\'' +
", userId='" + userId + '\'' +
", sort=" + sort +
", createdAt=" + createdAt +
", modifiedAt=" + modifiedAt +
", isFinished=" + isFinished +
'}';
}
}

View File

@ -0,0 +1,9 @@
package it.unisannio.ding.ids.wedroid.wrapper.entity;
public class ChecklistItemStatus {
private boolean isFinished;
public ChecklistItemStatus(boolean isFinished) {
this.isFinished = isFinished;
}
}

Some files were not shown because too many files have changed in this diff Show More