diff --git a/.idea/misc.xml b/.idea/misc.xml index af0bbdd..29bb4c5 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -5,7 +5,7 @@ - + diff --git a/app/build.gradle b/app/build.gradle index 40e8bf9..60f6666 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,6 +6,7 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' + apply plugin: 'io.gitlab.arturbosch.detekt' android { @@ -60,7 +61,8 @@ dependencies { // UI implementation "com.google.android.material:material:$rootProject.materialVersion" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" - + //Card view + implementation 'androidx.cardview:cardview:1.0.0' // TESTING testImplementation 'junit:junit:4.12' @@ -78,4 +80,5 @@ dependencies { androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion" androidTestImplementation "androidx.arch.core:core-testing:$rootProject.androidxArchVersion" androidTestImplementation "androidx.arch.core:core-testing:$rootProject.coreTestingVersion" + } diff --git a/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/data/dao/WListDaoTest.kt b/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/data/dao/WListDaoTest.kt new file mode 100644 index 0000000..8e9096d --- /dev/null +++ b/app/src/androidTest/java/it/unisannio/ding/ids/wedroid/app/data/dao/WListDaoTest.kt @@ -0,0 +1,111 @@ +package it.unisannio.ding.ids.wedroid.app.data.dao + +import android.content.Context +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import it.unisannio.ding.ids.wedroid.app.data.database.WListDatabase +import it.unisannio.ding.ids.wedroid.app.data.entity.WList +import it.unisannio.ding.ids.wedroid.app.observeOnce +import junit.framework.TestCase.assertEquals +import kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.io.IOException + +@RunWith(AndroidJUnit4::class) +class WListDaoTest { + private lateinit var dao: WListDao + private lateinit var db: WListDatabase + + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + @Before + fun createDb() { + val context = ApplicationProvider.getApplicationContext() + db = Room.inMemoryDatabaseBuilder( + context, WListDatabase::class.java + ).build() + dao = db.wListDao() + } + + @After + @Throws(IOException::class) + fun closeDb() { + db.close() + } + + @Test + fun emptyDatabaseOnCreation() { + dao.allWList.observeOnce { + assertEquals(0, it.size) + } + } + + @Test + fun insert() { + val wList = WList("id", "title") + + runBlocking { + dao.insert(wList) + } + + dao.allWList.observeOnce { + assertEquals(1, it.size) + assertEquals(wList, it[0]) + } + } + + @Test + fun replaceOnConflict() { + val wList0 = WList("id", "title0") + val wList1 = WList("id", "title1") + + runBlocking { + dao.insert(wList0) + dao.insert(wList1) + } + + dao.allWList.observeOnce { + assertEquals(1, it.size) + assertEquals("title1", it[0].title) + } + } + + @Test + fun getInAscendingOrder() { + val wList0 = WList("id0", "title0") + val wList1 = WList("id1", "title1") + + runBlocking { + dao.insert(wList1) + dao.insert(wList0) + } + + dao.allWList.observeOnce { + assertEquals(2, it.size) + assertEquals(wList0, it[0]) + assertEquals(wList1, it[1]) + } + } + + @Test + fun delete() { + val wlist = WList("id", "title") + + runBlocking { + dao.insert(wlist) + dao.delete(wlist) + } + + dao.allWList.observeOnce { + assertEquals(0, it.size) + } + } + +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 298ca17..d6d9155 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -10,24 +11,36 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/AppTheme"> + + android:theme="@style/AppTheme" + tools:ignore="AllowBackup,GoogleAppIndexingWarning"> + + + + + - + \ No newline at end of file diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/dao/WListDao.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/dao/WListDao.java new file mode 100644 index 0000000..3c74fe7 --- /dev/null +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/dao/WListDao.java @@ -0,0 +1,27 @@ +package it.unisannio.ding.ids.wedroid.app.data.dao; + +import androidx.lifecycle.LiveData; +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; + +import java.util.List; + +//import io.reactivex.Completable; +import it.unisannio.ding.ids.wedroid.app.data.entity.WList; + +@Dao +public interface WListDao { + + @Query("SELECT * from wlist_table ORDER BY title ASC") + LiveData> getAllWList(); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insert(WList wList); + + @Delete + void delete(WList wList); + +} diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/database/BoardDatabase.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/database/BoardDatabase.java index 07181ba..b9c6021 100644 --- a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/database/BoardDatabase.java +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/database/BoardDatabase.java @@ -12,6 +12,7 @@ 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) { diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/database/WListDatabase.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/database/WListDatabase.java new file mode 100644 index 0000000..f35da1e --- /dev/null +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/database/WListDatabase.java @@ -0,0 +1,31 @@ +package it.unisannio.ding.ids.wedroid.app.data.database; + +import android.content.Context; + +import androidx.room.Database; +import androidx.room.Room; +import androidx.room.RoomDatabase; + +import it.unisannio.ding.ids.wedroid.app.data.dao.WListDao; +import it.unisannio.ding.ids.wedroid.app.data.entity.WList; + +@Database(entities = WList.class, version = 2, exportSchema = false) +public abstract class WListDatabase extends RoomDatabase { + private static volatile WListDatabase INSTANCE; + + public abstract WListDao wListDao(); + + public static WListDatabase getDatabase(Context context) { + if (INSTANCE != null) + return INSTANCE; + synchronized (WListDatabase.class) { + INSTANCE = Room.databaseBuilder( + context.getApplicationContext(), + WListDatabase.class, + "wlist_database" + ).fallbackToDestructiveMigration().build(); + + return INSTANCE; + } + } +} diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/entity/WList.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/entity/WList.kt new file mode 100644 index 0000000..1fedd70 --- /dev/null +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/entity/WList.kt @@ -0,0 +1,15 @@ +package it.unisannio.ding.ids.wedroid.app.data.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "wlist_table") +data class WList( + @PrimaryKey @ColumnInfo(name = "id") val id: String, + @ColumnInfo(name = "title") val title: String = "" +) + +fun it.unisannio.ding.ids.wedroid.wrapper.entity.WList.convert(): WList { + return WList(this.id, this.title) +} diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepository.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepository.kt index 1218371..0c9e4de 100644 --- a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepository.kt +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepository.kt @@ -151,7 +151,7 @@ class BoardRepository( private suspend fun removeOldBoardsFromDb(boards: Collection) { allBoards.value?.minus(boards) ?.forEach { - dao.delete(it) + dao.delete(it) } } diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/WListRepository.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/WListRepository.kt new file mode 100644 index 0000000..c377bbc --- /dev/null +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/WListRepository.kt @@ -0,0 +1,116 @@ +package it.unisannio.ding.ids.wedroid.app.data.repository + +import android.util.Log + +import androidx.lifecycle.LiveData +import it.unisannio.ding.ids.wedroid.app.data.dao.WListDao +import it.unisannio.ding.ids.wedroid.app.data.entity.WList +import it.unisannio.ding.ids.wedroid.app.data.entity.convert +import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader +import it.unisannio.ding.ids.wedroid.wrapper.api.ListService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class WListRepository( + private val dao: WListDao, + private val service: ListService, + private val reader: PreferenceReader +) { + + val allWLists: LiveData> by lazy { + dao.allWList + } + + init { + synchronize() + } + + fun synchronize() { + service.getAllList(reader.boardId) + .enqueue(object : + Callback> { + override fun onFailure( + call: Call>, + t: Throwable + ) = logNetworkError(t.message) + + override fun onResponse( + call: Call>, + response: Response> + ) { + CoroutineScope(Dispatchers.IO).launch { + synchronizeCallback(response) + } + } + }) + } + + private fun synchronizeCallback( + response: Response> + ) { + if (!response.isSuccessful) { + logNetworkError("${response.code()} ${response.message()}") + return + } + + val wLists = (response.body() ?: return) + .map { it.convert() } + + addNewWListToDb(wLists) + removeOldWListsFromDb(wLists) + } + + fun deleteWList(idBoard: String, idWList: String) { + service.deleteList(idBoard, idWList).enqueue( + object : Callback { + override fun onFailure( + call: Call, + t: Throwable + ) { + logNetworkError(t.message) + } + + override fun onResponse( + call: Call, + response: Response + ) { + CoroutineScope(Dispatchers.IO).launch { + deleteWListCallBack(response, idWList) + } + } + }) + } + + private fun deleteWListCallBack( + response: Response, + id: String + ) { + if (!response.isSuccessful) { + logNetworkError("${response.code()} ${response.message()}") + return + } + dao.delete(WList(id)) + } + + private fun addNewWListToDb(wLists: Collection) { + wLists.forEach { + dao.insert(it) + } + } + + private fun removeOldWListsFromDb(wLists: Collection) { + allWLists.value?.minus(wLists) + ?.forEach { + dao.delete(it) + } + } + + @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") + private fun logNetworkError(message: String?) { + Log.e("RETROFIT", message) + } +} diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/PreferenceReader.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/PreferenceReader.java index 3fe9de0..2574e01 100644 --- a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/PreferenceReader.java +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/PreferenceReader.java @@ -2,6 +2,10 @@ package it.unisannio.ding.ids.wedroid.app.util; public interface PreferenceReader { String getBaseUrl(); + String getUserId(); + String getToken(); + + String getBoardId(); } diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/PreferenceWriter.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/PreferenceWriter.java index 264b507..74685c8 100644 --- a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/PreferenceWriter.java +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/PreferenceWriter.java @@ -2,6 +2,10 @@ package it.unisannio.ding.ids.wedroid.app.util; public interface PreferenceWriter { void setBaseUrl(String baseUrl); + void setUserId(String userId); + void setToken(String token); + + void setBoardId(String boardId); } diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/ServicesFactory.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/ServicesFactory.kt index eece43a..c3c3a83 100644 --- a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/ServicesFactory.kt +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/ServicesFactory.kt @@ -4,14 +4,14 @@ 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 it.unisannio.ding.ids.wedroid.wrapper.api.UserService import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory -class ServicesFactory private constructor( +class ServicesFactory( reader: PreferenceReader ) { private val retrofit: Retrofit diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/SharedPreferenceHelper.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/SharedPreferenceHelper.kt index 9f4f37d..e69ca56 100644 --- a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/SharedPreferenceHelper.kt +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/util/SharedPreferenceHelper.kt @@ -31,4 +31,13 @@ class SharedPreferenceHelper(context: Context) : PreferenceReader, PreferenceWri val editor = sp.edit() editor.putString("token", token).apply() } + + override fun getBoardId(): String? { + return sp.getString("boardId", "") + } + + override fun setBoardId(token: String?) { + val editor = sp.edit() + editor.putString("boardId", token).apply() + } } diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/BoardViewActivity.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/BoardViewActivity.java new file mode 100644 index 0000000..2b147a9 --- /dev/null +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/BoardViewActivity.java @@ -0,0 +1,231 @@ +package it.unisannio.ding.ids.wedroid.app.view; + +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Objects; + +import it.unisannio.ding.ids.wedroid.app.R; +import it.unisannio.ding.ids.wedroid.app.util.ServicesFactory; +import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper; +import it.unisannio.ding.ids.wedroid.wrapper.entity.Board; +import it.unisannio.ding.ids.wedroid.wrapper.entity.User; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class BoardViewActivity extends AppCompatActivity { + + static final int WLISTS_REQUEST = 30; + + private String idBoard, username, boardTitle; + private int boardColor; + private TextView description, members, permission, creationDate, lastModificationDate; + private View divider1, divider2, divider3; + private ListView listView; + private Button getListsButton; + private SharedPreferenceHelper sp; + private ServicesFactory service; + private Toolbar myToolbar; + private Board board; + private SwipeRefreshLayout swipeRefreshLayout; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_board_view); + + findViews(); + + setSupportActionBar(myToolbar); + + swipeRefreshLayout.setRefreshing(false); + + Intent i = getIntent(); + idBoard = i.getStringExtra("idBoard"); + + sp = new SharedPreferenceHelper(this); + sp.setBoardId(idBoard); + + initializeUI(idBoard); + + getListsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent i = new Intent(getApplicationContext(), WListsListActivity.class); + i.putExtra("idBoard", idBoard); + i.putExtra("barTitle", boardTitle); + i.putExtra("barColor", boardColor); + startActivityForResult(i, WLISTS_REQUEST); + } + }); + + swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + initializeUI(idBoard); + swipeRefreshLayout.setRefreshing(false); + } + }); + } + + private void findViews() { + myToolbar = findViewById(R.id.my_toolbar); + getListsButton = findViewById(R.id.getLists); + description = findViewById(R.id.descriptionTxt); + members = findViewById(R.id.membersTxt); + permission = findViewById(R.id.permissionTxt); + creationDate = findViewById(R.id.createdDate); + lastModificationDate = findViewById(R.id.modifiedDate); + divider1 = findViewById(R.id.divider1); + divider2 = findViewById(R.id.divider2); + divider3 = findViewById(R.id.divider3); + listView = findViewById(R.id.listViewID); + swipeRefreshLayout = findViewById(R.id.pullToRefresh); + } + + private void initializeUI(String idBoard) { + service = new ServicesFactory(sp); + service.getBoardService().getBoard(idBoard).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + assert response.body() != null; + board = response.body(); + if (board.getId() == null) { + Toast.makeText( + getApplicationContext(), + getApplicationContext().getString(R.string.board_deleted), + Toast.LENGTH_LONG) + .show(); + getListsButton.setEnabled(false); + } else { + Objects.requireNonNull(getSupportActionBar()).setTitle(board.getTitle()); + myToolbar.setBackgroundColor(Color.parseColor(encodeColor( + board.getBackgroundColor().toString()))); + + boardTitle = board.getTitle(); + + boardColor = Color.parseColor(encodeColor( + board.getBackgroundColor().toString())); + + Drawable background = getListsButton.getBackground(); + background.setTint(boardColor); + getListsButton.setBackgroundDrawable(background); + + description.setText(board.getDescription()); + permission.setText(board.getPermission().toString()); + members.setText(""); + for (int i = 0; i < board.getMembers().size(); i++) { + replaceIDUserToUsername(board.getMembers().get(i).getUserId()); + } + + creationDate.setText(R.string.created_at); + creationDate.append("\n" + + board.getCreatedAt()); + lastModificationDate.setText(R.string.modified_at); + lastModificationDate.append("\n" + + board.getModifiedAt()); + + divider1.setBackgroundColor(boardColor); + divider2.setBackgroundColor(boardColor); + divider3.setBackgroundColor(boardColor); + + ArrayList labelsTitle = new ArrayList<>(); + for (int i = 0; i < board.getLabels().size(); i++) { + labelsTitle.add(board.getLabels().get(i).getName()); + } + ArrayAdapter adapter = new ArrayAdapter<>(getApplicationContext(), + android.R.layout.simple_list_item_1, labelsTitle); + listView.setAdapter(adapter); + getListsButton.setEnabled(true); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull Throwable t) { + Toast.makeText(getApplicationContext(), + "connection error", + Toast.LENGTH_LONG).show(); + getListsButton.setEnabled(false); + } + }); + } + + private String encodeColor(String color) { + String encodedColor; + if (color.equalsIgnoreCase("belize")) { + encodedColor = "#2980B9"; + } else if (color.equalsIgnoreCase("nephritis")) { + encodedColor = "#27AE60"; + } else if (color.equalsIgnoreCase("pomegranate")) { + encodedColor = "#C0392B"; + } else if (color.equalsIgnoreCase("pumpkin")) { + encodedColor = "#E67E22"; + } else if (color.equalsIgnoreCase("wisteria")) { + encodedColor = "#8E44AD"; + } else if (color.equalsIgnoreCase("moderatepink")) { + encodedColor = "#CD5A91"; + } else if (color.equalsIgnoreCase("strongcyan")) { + encodedColor = "#00AECC"; + } else if (color.equalsIgnoreCase("dark")) { + encodedColor = "#2C3E51"; + } else if (color.equalsIgnoreCase("midnight")) { + encodedColor = "#2C3E50"; + } else if (color.equalsIgnoreCase("relax")) { + encodedColor = "#27AE61"; + } else if (color.equalsIgnoreCase("corteza")) { + encodedColor = "#568BA2"; + } else + encodedColor = "#38DF87"; + + return encodedColor; + } + + private void replaceIDUserToUsername(String idUser) { + service = new ServicesFactory(sp); + service.getUserService().getUser(idUser).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + User u = response.body(); + assert u != null; + username = u.getUsername(); + if (u.isAdmin()) { + members.append("Admin: " + username + ";\n"); + } else + members.append("Other member: " + username + ";\n"); + } + + @Override + public void onFailure(@NotNull Call call, @NotNull Throwable t) { + //TODO + } + }); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == WLISTS_REQUEST) { + if (resultCode != WListsListActivity.RESULT_OK) { + Toast.makeText(this, "It's not possible view the lists", + Toast.LENGTH_LONG).show(); + } + } + } +} diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/LoginActivity.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/LoginActivity.kt index 811db1a..556ad04 100644 --- a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/LoginActivity.kt +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/LoginActivity.kt @@ -42,7 +42,7 @@ class LoginActivity : AppCompatActivity() { val passwordText = password.text.toString() val instanceServerText = instanceServer.text.toString() - if (!URLUtil.isValidUrl(instanceServerText)){ + if (!URLUtil.isValidUrl(instanceServerText)) { Toast.makeText(this, R.string.login_unformed_instance, Toast.LENGTH_LONG) .show() return diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/WListsListActivity.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/WListsListActivity.java new file mode 100644 index 0000000..e42b9f9 --- /dev/null +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/WListsListActivity.java @@ -0,0 +1,118 @@ +package it.unisannio.ding.ids.wedroid.app.view; + +import android.content.Intent; +import android.content.res.ColorStateList; +import android.os.Bundle; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; + +import java.util.List; +import java.util.Objects; + +import it.unisannio.ding.ids.wedroid.app.R; + +import it.unisannio.ding.ids.wedroid.app.data.entity.WList; +import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper; +import it.unisannio.ding.ids.wedroid.app.view.adapter.WListsAdapter; +import it.unisannio.ding.ids.wedroid.app.viewmodel.WListsListViewModel; + +public class WListsListActivity extends AppCompatActivity { + + Toolbar myToolbar; + int barColor; + WListsListViewModel viewModel; + RecyclerView recyclerView; + //SwipeRefreshLayout swipeRefreshLayout; + SharedPreferenceHelper sp; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_wlists_view); + myToolbar = findViewById(R.id.my_toolbar); + setSupportActionBar(myToolbar); + //swipeRefreshLayout = findViewById(R.id.pullToRefresh); + sp = new SharedPreferenceHelper(this); + + Intent i = getIntent(); + String boardTitle = i.getStringExtra("barTitle"); + Objects.requireNonNull(getSupportActionBar()).setTitle(boardTitle); + barColor = i.getIntExtra("barColor", 0); + setResult(WListsListActivity.RESULT_OK, i); + + myToolbar.setBackgroundColor(barColor); + + recyclerView = findViewById(R.id.recyclerviewWList); + initializeUi(sp.getBoardId()); + + FloatingActionButton fab = findViewById(R.id.synchronize); + fab.setBackgroundTintList(ColorStateList.valueOf(barColor)); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + viewModel.refresh(); + } + }); + } + + private void initializeUi(final String idBoard) { + final WListsAdapter adapter = new WListsAdapter(this); + recyclerView.setAdapter(adapter); + recyclerView.setLayoutManager(new LinearLayoutManager(this, + LinearLayoutManager.HORIZONTAL, false)); + //recyclerView.setHasFixedSize(true); + + viewModel = new ViewModelProvider(this).get(WListsListViewModel.class); + + viewModel.getAllWLists().observe(this, new Observer>() { + @Override + public void onChanged(List wLists) { + adapter.setWLists(wLists); + } + }); + + swipeBottomToDelete(idBoard); + + /* BUG REPORT: Refresh of page enter in conflict with swipe down for delete list + * swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + viewModel.refresh(); + } + });*/ + } + + private void swipeBottomToDelete(final String idBoard) { + ItemTouchHelper.SimpleCallback simpleCallback = new ItemTouchHelper.SimpleCallback(0, + ItemTouchHelper.DOWN) { + @Override + public boolean onMove(@NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder, + @NonNull RecyclerView.ViewHolder target) { + + return true; + } + + @Override + public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { + int pos = viewHolder.getAdapterPosition(); + viewModel.deleteWList(pos, idBoard); + Toast.makeText(getApplicationContext(), "List deleted", Toast.LENGTH_LONG).show(); + + } + }; + new ItemTouchHelper(simpleCallback).attachToRecyclerView(recyclerView); + } +} diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/adapter/BoardsListAdapter.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/adapter/BoardsListAdapter.kt index 7ea269c..b62567f 100644 --- a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/adapter/BoardsListAdapter.kt +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/adapter/BoardsListAdapter.kt @@ -9,6 +9,7 @@ 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 @@ -37,9 +38,10 @@ class BoardsListAdapter internal constructor( holder.boardTitle.text = board.title holder.itemView.setOnClickListener { - val intent = Intent(it.context, TODO()) - intent.putExtra(BOARD_ID, board.id) - it.context.startActivity(intent) + + val intent = Intent(holder.itemView.context, BoardViewActivity::class.java) + intent.putExtra("idBoard", board.id) + holder.itemView.context.startActivity(intent) } } diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/adapter/WListsAdapter.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/adapter/WListsAdapter.java new file mode 100644 index 0000000..dfe929d --- /dev/null +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/view/adapter/WListsAdapter.java @@ -0,0 +1,201 @@ +package it.unisannio.ding.ids.wedroid.app.view.adapter; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.DialogInterface; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.RecyclerView; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +import it.unisannio.ding.ids.wedroid.app.R; +import it.unisannio.ding.ids.wedroid.app.data.entity.WList; +import it.unisannio.ding.ids.wedroid.app.util.ServicesFactory; +import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper; +import it.unisannio.ding.ids.wedroid.wrapper.entity.Card; +import it.unisannio.ding.ids.wedroid.wrapper.entity.Swimlane; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class WListsAdapter extends RecyclerView.Adapter { + + private final LayoutInflater mInflater; + private List mWLists; // Cached copy of lists + private Context mContext; + private ServicesFactory service; + private SharedPreferenceHelper sp; + + public WListsAdapter(Context context) { + mInflater = LayoutInflater.from(context); + mContext = context; + sp = new SharedPreferenceHelper(mContext); + service = new ServicesFactory(sp); + } + + class WListViewHolder extends RecyclerView.ViewHolder { + private final TextView wListItemView; + ListView listView; + Button button; + + private WListViewHolder(View itemView) { + super(itemView); + wListItemView = itemView.findViewById(R.id.wListTitle); + listView = itemView.findViewById(R.id.listViewCard); + button = itemView.findViewById(R.id.buttonAddCard); + } + } + + @NonNull + @Override + public WListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View itemView = mInflater.inflate(R.layout.wlist_recyclerview_item, parent, false); + return new WListViewHolder(itemView); + } + + @SuppressLint("SetTextI18n") + @Override + public void onBindViewHolder(@NonNull final WListViewHolder holder, int position) { + if (mWLists != null) { + final WList current = mWLists.get(position); + final List cardTitle = new ArrayList<>(); + service.getCardService().getAllCards(sp.getBoardId(), current.getId()).enqueue( + new Callback>() { + @Override + public void onResponse(@NotNull Call> call, @NotNull Response> response) { + assert response.body() != null; + for (int i = 0; i < response.body().size(); i++) { + cardTitle.add(response.body().get(i).getTitle()); + } + ArrayAdapter adapter = new ArrayAdapter<>(mContext, + android.R.layout.simple_list_item_1, cardTitle); + holder.listView.setAdapter(adapter); + holder.wListItemView.setText(current.getTitle()); + holder.button.setText("Add Card to " + current.getTitle()); + holder.button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showInputDialog(current.getId()); + } + }); + } + + @Override + public void onFailure(@NotNull Call> call, @NotNull Throwable t) { + } + }); + + /*position of the old content holder**/ + + } else { + // Covers the case of data not being ready yet. + holder.wListItemView.setText("No wList"); + } + } + + @Override + public int getItemCount() { + if (mWLists != null) + return mWLists.size(); + else return 0; + } + + public void setWLists(List wList) { + mWLists = wList; + notifyDataSetChanged(); + } + + private void showInputDialog(final String current) { + LayoutInflater layoutInflater = LayoutInflater.from(mContext); + @SuppressLint("InflateParams") View promptView = layoutInflater.inflate( + R.layout.alert_new_card, null); + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(mContext); + alertDialogBuilder.setView(promptView); + + final EditText editText = promptView.findViewById(R.id.edittext); + // setup a dialog window + alertDialogBuilder.setCancelable(false) + .setPositiveButton("OK", new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int id) { + if (!editText.getText().toString().matches("")) { + service.getSwimlanesService().getAllSwimlanes(sp.getBoardId()) + .enqueue(new Callback>() { + @Override + public void onResponse(@NotNull Call> call, + @NotNull Response> response) { + String idDefaultSwimlane = null; + assert response.body() != null; + for (Swimlane swim : response.body()) { + if (swim.getTitle().equalsIgnoreCase("default")) + idDefaultSwimlane = swim.getTitle(); + } + final Card card = new Card(); + card.setTitle(editText.getText().toString()); + card.setAuthorId(sp.getUserId()); + card.setSwimlaneId(idDefaultSwimlane); + //card.setDescription("new card from app"); + service.getListService().getList(sp.getBoardId(), current).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + + service.getCardService().newCard(sp.getBoardId(), current, card).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + if (response.isSuccessful()) + Toast.makeText(mContext, "card posted", + Toast.LENGTH_LONG).show(); + else + Toast.makeText(mContext, "card doesn't posted", + Toast.LENGTH_LONG).show(); + } + + @Override + public void onFailure(@NotNull Call call, @NotNull Throwable t) { + + } + }); + } + + @Override + public void onFailure(@NotNull Call call, @NotNull Throwable t) { + + } + }); + } + + @Override + public void onFailure(@NotNull Call> call, @NotNull Throwable t) { + + } + }); + } else + Toast.makeText(mContext, "cancel", Toast.LENGTH_LONG).show(); + } + }) + .setNegativeButton("Cancel", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + // create an alert dialog + AlertDialog alert = alertDialogBuilder.create(); + alert.show(); + } +} diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/viewmodel/WListsListViewModel.java b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/viewmodel/WListsListViewModel.java new file mode 100644 index 0000000..e5281a5 --- /dev/null +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/viewmodel/WListsListViewModel.java @@ -0,0 +1,47 @@ +package it.unisannio.ding.ids.wedroid.app.viewmodel; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; + +import java.util.List; + +import it.unisannio.ding.ids.wedroid.app.data.database.WListDatabase; +import it.unisannio.ding.ids.wedroid.app.data.entity.WList; +import it.unisannio.ding.ids.wedroid.app.data.repository.WListRepository; +import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader; +import it.unisannio.ding.ids.wedroid.app.util.ServicesFactory; +import it.unisannio.ding.ids.wedroid.app.util.SharedPreferenceHelper; + + +public class WListsListViewModel extends AndroidViewModel { + private WListRepository wListRepository; + private LiveData> allWLists; + + public WListsListViewModel(@NonNull Application application) { + super(application); + PreferenceReader reader = new SharedPreferenceHelper(application); + wListRepository = new WListRepository( + WListDatabase.getDatabase(application).wListDao(), + ServicesFactory.Companion.getInstance(reader).getListService(), + reader); + allWLists = wListRepository.getAllWLists(); + } + + public LiveData> getAllWLists(){ + return allWLists; + } + + public void deleteWList(int position, String idBoard) { + List wList = allWLists.getValue(); + + if (wList != null) + wListRepository.deleteWList(idBoard, wList.get(position).getId()); + } + + public void refresh() { + wListRepository.synchronize(); + } +} diff --git a/app/src/main/res/drawable/rounded_shape.xml b/app/src/main/res/drawable/rounded_shape.xml new file mode 100644 index 0000000..255bc75 --- /dev/null +++ b/app/src/main/res/drawable/rounded_shape.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_board_view.xml b/app/src/main/res/layout/activity_board_view.xml new file mode 100644 index 0000000..04c8d00 --- /dev/null +++ b/app/src/main/res/layout/activity_board_view.xml @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_wlists_view.xml b/app/src/main/res/layout/activity_wlists_view.xml new file mode 100644 index 0000000..539c2bc --- /dev/null +++ b/app/src/main/res/layout/activity_wlists_view.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/alert_new_card.xml b/app/src/main/res/layout/alert_new_card.xml new file mode 100644 index 0000000..1ffd286 --- /dev/null +++ b/app/src/main/res/layout/alert_new_card.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/wlist_recyclerview_item.xml b/app/src/main/res/layout/wlist_recyclerview_item.xml new file mode 100644 index 0000000..7b4a3ab --- /dev/null +++ b/app/src/main/res/layout/wlist_recyclerview_item.xml @@ -0,0 +1,28 @@ + + + + + + + +