Resolved merge conflict

This commit is contained in:
Umberto Furno 2020-01-08 15:39:33 +01:00
commit bb4b4a91c8
37 changed files with 2406 additions and 113 deletions

View File

@ -5,7 +5,7 @@
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
</configurations>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<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">

View File

@ -6,6 +6,9 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'io.gitlab.arturbosch.detekt'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
@ -29,7 +32,10 @@ android {
}
dependencies {
// standard
implementation project(':wrapper')
detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:1.2.2"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
@ -58,13 +64,15 @@ dependencies {
//Card view
implementation 'androidx.cardview:cardview:1.0.0'
// TESTING
testImplementation 'junit:junit:4.12'
testImplementation "io.mockk:mockk:1.9.3"
testImplementation "com.squareup.okhttp3:mockwebserver:4.2.1"
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
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'
implementation "com.squareup.retrofit2:retrofit:2.6.2"
implementation "com.squareup.retrofit2:converter-gson:2.6.2"

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

@ -11,14 +11,26 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".DriverActivity">
<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.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>
<activity android:name=".view.BoardViewActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<activity android:name=".view.WListsListActivity"

View File

@ -1,46 +0,0 @@
package it.unisannio.ding.ids.wedroid.app;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper;
import it.unisannio.ding.ids.wedroid.app.view.BoardViewActivity;
public class DriverActivity extends AppCompatActivity {
SharedPreferenceHelper sharedPreferences;
EditText idBoard;
Button send;
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sharedPreferences = new SharedPreferenceHelper(this);
String baseUrl = sharedPreferences.getBaseUrl();
String token = sharedPreferences.getToken();
String userId = sharedPreferences.getUserId();
sharedPreferences.setBaseUrl("https://board.norangeb.it/");
sharedPreferences.setToken("4waGrVlk0fkLhiQRDgN_rkbIamp4IyB6mThS0IpKbPx");
sharedPreferences.setUserId("jPdkf3a9bmfZWx3GR");
idBoard = findViewById(R.id.idBoard);
send= findViewById(R.id.send);
send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(getApplicationContext(), BoardViewActivity.class);
i.putExtra("idBoard", idBoard.getText().toString());
startActivity(i);
}
});
}
}

View File

@ -1,15 +0,0 @@
package it.unisannio.ding.ids.wedroid.app
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import it.unisannio.ding.ids.wedroid.wrapper.api.BoardService
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val service : BoardService? = null
}
}

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

@ -1,10 +1,17 @@
package it.unisannio.ding.ids.wedroid.app.util
import it.unisannio.ding.ids.wedroid.wrapper.api.*
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.ListService
import it.unisannio.ding.ids.wedroid.wrapper.api.SwimlanesService
import it.unisannio.ding.ids.wedroid.wrapper.api.UserService
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class ServicesFactory(
reader: PreferenceReader
) {
@ -75,4 +82,3 @@ class ServicesFactory(
}
}
}

View File

@ -2,7 +2,8 @@ package it.unisannio.ding.ids.wedroid.app.util
import android.content.Context
class SharedPreferenceHelper(context : Context) : PreferenceReader, PreferenceWriter {
class SharedPreferenceHelper(context: Context) : PreferenceReader, PreferenceWriter {
private val sp = context.getSharedPreferences("userinfo", Context.MODE_PRIVATE)
override fun getBaseUrl(): String? {
@ -20,8 +21,7 @@ class SharedPreferenceHelper(context : Context) : PreferenceReader, PreferenceWr
override fun setBaseUrl(baseUrl: String?) {
val editor = sp.edit()
editor.putString("url", baseUrl).apply()
}
}
override fun setUserId(userId: String?) {
val editor = sp.edit()
@ -42,3 +42,4 @@ class SharedPreferenceHelper(context : Context) : PreferenceReader, PreferenceWr
editor.putString("boardId", token).apply()
}
}

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,97 @@
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() != 200) {
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
}
}

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,55 @@
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(it.context, BoardViewActivity::class.java)
intent.putExtra("idBoard", board.id)
it.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,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,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,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="100dp"
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="50dp"
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

@ -1,40 +0,0 @@
<?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=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.033"
tools:ignore="MissingConstraints"
tools:layout_editor_absoluteX="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="1dp"
android:text="Activity driver that passed id board" />
<EditText
android:id="@+id/idBoard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
android:text="ID_BOARD" />
<Button
android:id="@+id/send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Send" />
</LinearLayout>
</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,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

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

View File

@ -12,4 +12,34 @@
<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-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,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

@ -5,21 +5,24 @@ buildscript {
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"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
apply plugin: 'checkstyle'
repositories {
google()
jcenter()
}
}

View File

@ -0,0 +1,241 @@
<?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="NeedBraces"/>
<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="VariableDeclarationUsageDistance"/>
<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.*'

View File

@ -10,7 +10,7 @@ public enum BoardBackgroundColor {
@SerializedName("pomegranate")
POMEGRANATE,
@SerializedName("pumpkin")
PUMPIK,
PUMPKIN,
@SerializedName("wisteria")
WISTERIA,
@SerializedName("moderatepink")