Skip to content

Commit

Permalink
Merge pull request #557 from jorgeblacio/delete_labels
Browse files Browse the repository at this point in the history
Now you can delete labels.
  • Loading branch information
danieltigse authored Oct 24, 2019
2 parents efbd500 + df4464a commit 5aae486
Show file tree
Hide file tree
Showing 28 changed files with 367 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.criptext.mail.scenes.settings.data

import androidx.test.rule.ActivityTestRule
import androidx.test.runner.AndroidJUnit4
import com.criptext.mail.Config
import com.criptext.mail.androidtest.TestActivity
import com.criptext.mail.androidtest.TestDatabase
import com.criptext.mail.api.HttpClient
import com.criptext.mail.db.KeyValueStorage
import com.criptext.mail.db.SettingsLocalDB
import com.criptext.mail.db.models.Account
import com.criptext.mail.db.models.ActiveAccount
import com.criptext.mail.db.models.Contact
import com.criptext.mail.db.models.Label
import com.criptext.mail.scenes.settings.labels.data.LabelsResult
import com.criptext.mail.scenes.settings.labels.workers.CreateCustomLabelWorker
import com.criptext.mail.scenes.settings.labels.workers.DeleteCustomLabelWorker
import com.criptext.mail.utils.ExpectedRequest
import com.criptext.mail.utils.MockedResponse
import com.criptext.mail.utils.assertSentRequests
import com.criptext.mail.utils.enqueueResponses
import io.mockk.mockk
import okhttp3.mockwebserver.MockWebServer
import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldEqual
import org.amshove.kluent.shouldNotBe
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class DeleteCustomLabelWorkerTest{

@get:Rule
val mActivityRule = ActivityTestRule(TestActivity::class.java)

private lateinit var storage: KeyValueStorage
private lateinit var db: TestDatabase
private lateinit var settingsLocalDB: SettingsLocalDB
private lateinit var mockWebServer: MockWebServer
private val activeAccount = ActiveAccount(name = "Tester", recipientId = "tester",
deviceId = 1, jwt = "__JWTOKEN__", signature = "", refreshToken = "", id = 1,
domain = Contact.mainDomain)
private lateinit var httpClient: HttpClient

@Before
fun setup() {
// mock http requests
mockWebServer = MockWebServer()
mockWebServer.start()
storage = mockk(relaxed = true)
val mockWebServerUrl = mockWebServer.url("/mock").toString()
httpClient = HttpClient.Default(authScheme = HttpClient.AuthScheme.jwt,
baseUrl = mockWebServerUrl, connectionTimeout = 1000L, readTimeout = 1000L)
db = TestDatabase.getInstance(mActivityRule.activity)
db.resetDao().deleteAllData(1)
db.labelDao().insertAll(Label.DefaultItems().toList())
db.accountDao().insert(Account(activeAccount.id, activeAccount.recipientId, activeAccount.deviceId,
activeAccount.name, activeAccount.jwt, activeAccount.refreshToken,
"_KEY_PAIR_", 0, "", "criptext.com",
true, true,
backupPassword = null, autoBackupFrequency = 0, hasCloudBackup = false, wifiOnly = true, lastTimeBackup = null))
settingsLocalDB = SettingsLocalDB.Default(db)
}

@Test
fun test_should_create_and_delete_a_custom_label(){
if (Config.mockCriptextHTTPRequests) {
mockWebServer.enqueueResponses(listOf(
MockedResponse.Ok(""),
MockedResponse.Ok("")
))
}
val labelName = "__LABEL__"
val createWorker = createLabelWorker(labelName)
val createResult = createWorker.work(mockk()) as LabelsResult.CreateCustomLabel.Success

createResult.label.text shouldEqual labelName

db.labelDao().get(labelName, activeAccount.id) shouldNotBe null

val labelUUID = createResult.label.uuid
val deleteWorker = deleteLabelWorker(labelUUID)
val deleteResult = deleteWorker.work(mockk()) as LabelsResult.DeleteCustomLabel.Success

deleteResult.uuid shouldEqual labelUUID

db.labelDao().getByUUID(labelUUID, activeAccount.id) shouldBe null
}

private fun deleteLabelWorker(uuid: String): DeleteCustomLabelWorker =
DeleteCustomLabelWorker(
labelUUID = uuid,
settingsLocalDB = settingsLocalDB,
activeAccount = activeAccount,
httpClient = httpClient,
publishFn = {},
storage = storage)

private fun createLabelWorker(labelName: String): CreateCustomLabelWorker =
CreateCustomLabelWorker(
labelName = labelName,
settingsLocalDB = settingsLocalDB,
activeAccount = activeAccount,
httpClient = httpClient,
publishFn = {},
storage = storage)

@After
fun teardown() {
if (Config.mockCriptextHTTPRequests)
mockWebServer.close()
}
}
1 change: 1 addition & 0 deletions src/main/kotlin/com/criptext/mail/api/models/Event.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ data class Event(val rowid: Long, val recipientId: String, val domain: String, v
const val recoveryEmailChanged = 311
const val recoveryEmailConfirmed = 312
const val profilePictureChanged = 313
const val peerLabelDeleted = 320

//Sync Devices
const val syncBeginRequest = 211
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.criptext.mail.api.models


import org.json.JSONObject

/**
* * data class for per label status updates. This is received as params of a "peer label deleted status update" event (320).
*/

data class PeerLabelDeletedStatusUpdate (val uuid: String) {
companion object {

fun fromJSON(jsonString: String): PeerLabelDeletedStatusUpdate {
val json = JSONObject(jsonString)
return PeerLabelDeletedStatusUpdate(
uuid = json.getString("uuid")
)
}
}
}
7 changes: 7 additions & 0 deletions src/main/kotlin/com/criptext/mail/db/EventLocalDB.kt
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ class EventLocalDB(private val db: AppDatabase, private val filesDir: File, priv
))
}

fun updateDeleteLabel(uuid: String, accountId: Long) {
db.labelDao().deleteByLabelUUID(
uuid = uuid,
accountId = accountId
)
}

fun updateDeleteThreadPermanently(threadIds: List<String>, activeAccount: ActiveAccount) {
if(threadIds.isNotEmpty()){
db.emailDao().getEmailsFromThreadIds(threadIds, activeAccount.id).forEach {
Expand Down
8 changes: 8 additions & 0 deletions src/main/kotlin/com/criptext/mail/db/dao/LabelDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,19 @@ interface LabelDao {
@Delete
fun delete(label: Label)

@Query("""DELETE FROM label
WHERE uuid=:uuid AND accountId = :accountId""")
fun deleteByLabelUUID(uuid: String, accountId: Long)

@Query("""SELECT * FROM label
WHERE text=:labelName AND (accountId IS NULL OR accountId = :accountId)
LIMIT 1""")
fun get(labelName: String, accountId: Long): Label

@Query("""SELECT * FROM label
WHERE uuid=:uuid AND accountId = :accountId""")
fun getByUUID(uuid: String, accountId: Long): Label?

@Query("""select CAST(COUNT(*) AS BIT) FROM label WHERE text=:labelName""")
fun alreadyExists(labelName: String): Boolean

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.graphics.PorterDuff
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.criptext.mail.R
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class LabelHolder(val view: View) : RecyclerView.ViewHolder(view), View.OnClickL
private val nameView : TextView
private val checkBoxView : CheckBox
private val labelColor: ImageView
private val trashImage: ImageView
private val viewSeparator: View

init {
Expand All @@ -31,13 +32,15 @@ class LabelHolder(val view: View) : RecyclerView.ViewHolder(view), View.OnClickL
nameView.text = labelThread.text
checkBoxView.isChecked = labelThread.isSelected
viewSeparator.visibility = View.GONE
trashImage.visibility = View.GONE
DrawableCompat.setTint(labelColor.drawable, Color.parseColor("#${labelThread.color}"))
}

init {
nameView = view.findViewById(R.id.label_name) as TextView
checkBoxView = view.findViewById(R.id.label_checkbox) as CheckBox
labelColor = view.findViewById(R.id.label_color)
trashImage = view.findViewById(R.id.label_trash)
viewSeparator = view.findViewById(R.id.viewSeparator)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class LabelHolder(val view: View) : RecyclerView.ViewHolder(view){
private val nameView : TextView
private val checkBoxView : CheckBox
private val labelColor: ImageView
private val trashImage: ImageView
private val rootView: View
private val viewSeparator: View

Expand All @@ -31,9 +32,11 @@ class LabelHolder(val view: View) : RecyclerView.ViewHolder(view){
nameView = view.findViewById(R.id.label_name) as TextView
checkBoxView = view.findViewById(R.id.label_checkbox) as CheckBox
labelColor = view.findViewById(R.id.label_color)
trashImage = view.findViewById(R.id.label_trash)
rootView = view.findViewById(R.id.rootView)
viewSeparator = view.findViewById(R.id.viewSeparator)
checkBoxView.visibility = View.INVISIBLE
trashImage.visibility = View.GONE
viewSeparator.visibility = View.GONE
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.criptext.mail.utils.generaldatasource.data.GeneralDataSource
import com.criptext.mail.utils.generaldatasource.data.GeneralRequest
import com.criptext.mail.utils.generaldatasource.data.GeneralResult
import com.criptext.mail.utils.generaldatasource.data.UserDataWriter
import com.criptext.mail.utils.ui.data.DialogData
import com.criptext.mail.utils.ui.data.DialogResult
import com.criptext.mail.utils.ui.data.DialogType
import com.criptext.mail.websocket.WebSocketEventListener
Expand Down Expand Up @@ -61,10 +62,20 @@ class LabelsController(
when(result) {
is LabelsResult.GetCustomLabels -> onGetCustomLabels(result)
is LabelsResult.CreateCustomLabel -> onCreateCustomLabels(result)
is LabelsResult.DeleteCustomLabel -> onDeleteCustomLabel(result)
}
}

private val labelsUIObserver = object: LabelsUIObserver{
override fun onDeleteLabelClicked(label: LabelWrapper) {
model.lastSelectedUUID = label.label.uuid
scene.showLabelDeleteDialog(DialogData.DialogConfirmationData(
title = UIMessage(R.string.title_delete_label),
message = listOf(UIMessage(R.string.message_delete_label, arrayOf(label.text))),
type = DialogType.DeleteLabel()
))
}

override fun onToggleLabelSelection(label: LabelWrapper) {
dataSource.submitRequest(LabelsRequest.ChangeVisibilityLabel(label.id, label.isSelected))
}
Expand Down Expand Up @@ -102,6 +113,9 @@ class LabelsController(
}
is DialogType.SignIn ->
host.goToScene(SignInParams(true), true)
is DialogType.DeleteLabel -> {
dataSource.submitRequest(LabelsRequest.DeleteCustomLabel(model.lastSelectedUUID))
}
}
}
}
Expand Down Expand Up @@ -241,6 +255,20 @@ class LabelsController(
}
}

private fun onDeleteCustomLabel(result: LabelsResult.DeleteCustomLabel){
model.lastSelectedUUID = ""
when(result) {
is LabelsResult.DeleteCustomLabel.Success -> {
val index = model.labels.indexOfFirst { it.label.uuid == result.uuid }
if(index >= 0)
labelWrapperListController.remove(index)
}
is LabelsResult.DeleteCustomLabel.Failure -> {
scene.showMessage(UIMessage(R.string.error_deleting_label))
}
}
}

private fun onGetCustomLabels(result: LabelsResult.GetCustomLabels){
when(result) {
is LabelsResult.GetCustomLabels.Success -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ import com.criptext.mail.scenes.label_chooser.data.LabelWrapper

class LabelsModel: SceneModel{
val labels : ArrayList<LabelWrapper> = ArrayList()
var lastSelectedUUID = ""
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.criptext.mail.utils.UIMessage
import com.criptext.mail.utils.UIUtils
import com.criptext.mail.utils.getLocalizedUIMessage
import com.criptext.mail.utils.ui.*
import com.criptext.mail.utils.ui.data.DialogData
import com.criptext.mail.utils.ui.data.DialogType
import com.criptext.mail.utils.uiobserver.UIObserver
import com.criptext.mail.utils.virtuallist.VirtualListView
Expand All @@ -37,6 +38,7 @@ interface LabelsScene{
fun showCreateLabelDialog(keyboardManager: KeyboardManager)
fun showAccountSuspendedDialog(observer: UIObserver, email: String, dialogType: DialogType)
fun dismissAccountSuspendedDialog()
fun showLabelDeleteDialog(dialogData: DialogData.DialogConfirmationData)

class Default(val view: View): LabelsScene{
private lateinit var labelsUIObserver: LabelsUIObserver
Expand All @@ -58,6 +60,7 @@ interface LabelsScene{
private val syncAuthDialog = SyncDeviceAlertDialog(context)
private val settingCustomLabelDialog = SettingsCustomLabelDialog(context)
private val accountSuspended = AccountSuspendedDialog(context)
private var generalDialogConfirmation: GeneralDialogConfirmation? = null

override fun attachView(labelsUIObserver: LabelsUIObserver,
model: LabelsModel) {
Expand Down Expand Up @@ -125,6 +128,11 @@ interface LabelsScene{
accountSuspended.showDialog(observer, email, dialogType)
}

override fun showLabelDeleteDialog(dialogData: DialogData.DialogConfirmationData) {
generalDialogConfirmation = GeneralDialogConfirmation(context, dialogData)
generalDialogConfirmation?.showDialog(labelsUIObserver)
}

override fun getLabelLocalizedName(name: String): String {
return context.getLocalizedUIMessage(
UIUtils.getLocalizedSystemLabelName(name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ interface LabelsUIObserver: UIObserver {
fun onCustomLabelNameAdded(labelName: String)
fun onCreateLabelClicked()
fun onToggleLabelSelection(label: LabelWrapper)
fun onDeleteLabelClicked(label: LabelWrapper)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ class LabelHolder(val view: View) : RecyclerView.ViewHolder(view){
private val nameView : TextView
private val checkBoxView : CheckBox
private val labelColor: ImageView
private val trashImage: ImageView

fun bindLabel(label: LabelWrapper) {
nameView.text = label.text
checkBoxView.isChecked = label.isSelected
if(label.type == LabelTypes.SYSTEM){
checkBoxView.isChecked = true
checkBoxView.isEnabled = false
trashImage.visibility = View.GONE
}
else{
checkBoxView.isEnabled = true
Expand All @@ -38,11 +40,18 @@ class LabelHolder(val view: View) : RecyclerView.ViewHolder(view){
nameView = view.findViewById(R.id.label_name) as TextView
checkBoxView = view.findViewById(R.id.label_checkbox) as CheckBox
labelColor = view.findViewById(R.id.label_color)
trashImage = view.findViewById(R.id.label_trash) as ImageView
}

fun setOnCheckboxClickedListener(onCheckboxClick: () -> Unit) {
checkBoxView.setOnClickListener {
onCheckboxClick()
}
}

fun setOnTrashClickedListener(onTrashClicked: () -> Unit){
trashImage.setOnClickListener {
onTrashClicked()
}
}
}
Loading

0 comments on commit 5aae486

Please sign in to comment.