Skip to content

Commit

Permalink
Merge pull request #4392 from nextcloud/restore_user_status
Browse files Browse the repository at this point in the history
Restore user status
  • Loading branch information
sowjanyakch authored Nov 14, 2024
2 parents 5ff1003 + b558a71 commit b361d56
Show file tree
Hide file tree
Showing 13 changed files with 242 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ import com.nextcloud.talk.models.json.status.predefined.PredefinedStatus

interface PredefinedStatusClickListener {
fun onClick(predefinedStatus: PredefinedStatus)
fun revertStatus()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import com.nextcloud.talk.models.json.status.predefined.PredefinedStatus

class PredefinedStatusListAdapter(
private val clickListener: PredefinedStatusClickListener,
val context: Context
val context: Context,
var isBackupStatusAvailable: Boolean
) : RecyclerView.Adapter<PredefinedStatusViewHolder>() {
internal var list: List<PredefinedStatus> = emptyList()

Expand All @@ -26,7 +27,7 @@ class PredefinedStatusListAdapter(
}

override fun onBindViewHolder(holder: PredefinedStatusViewHolder, position: Int) {
holder.bind(list[position], clickListener, context)
holder.bind(list[position], clickListener, context, isBackupStatusAvailable)
}

override fun getItemCount(): Int {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package com.nextcloud.talk.adapters

import android.content.Context
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.nextcloud.talk.R
import com.nextcloud.talk.databinding.PredefinedStatusBinding
Expand All @@ -16,9 +17,15 @@ import com.nextcloud.talk.utils.DisplayUtils

private const val ONE_SECOND_IN_MILLIS = 1000

@Suppress("DEPRECATION")
class PredefinedStatusViewHolder(private val binding: PredefinedStatusBinding) : RecyclerView.ViewHolder(binding.root) {

fun bind(status: PredefinedStatus, clickListener: PredefinedStatusClickListener, context: Context) {
fun bind(
status: PredefinedStatus,
clickListener: PredefinedStatusClickListener,
context: Context,
isBackupStatusAvailable: Boolean
) {
binding.root.setOnClickListener { clickListener.onClick(status) }
binding.icon.text = status.icon
binding.name.text = status.message
Expand All @@ -40,5 +47,14 @@ class PredefinedStatusViewHolder(private val binding: PredefinedStatusBinding) :
}
}
}
if (isBackupStatusAvailable) {
binding.resetStatusButton.visibility = if (position == 0) View.VISIBLE else View.GONE
if (position == 0) {
binding.clearAt.text = context.getString(R.string.previously_set)
}
binding.resetStatusButton.setOnClickListener {
clickListener.revertStatus()
}
}
}
}
10 changes: 6 additions & 4 deletions app/src/main/java/com/nextcloud/talk/api/NcApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,9 @@ Observable<SignalingOverall> pullSignalingMessages(@Nullable @Header("Authorizat
@GET
Observable<UserProfileOverall> getUserData(@Header("Authorization") String authorization, @Url String url);

@DELETE
Observable<GenericOverall> revertStatus(@Header("Authentication") String authorization, @Url String url);

@FormUrlEncoded
@PUT
Observable<GenericOverall> setUserData(@Header("Authorization") String authorization,
Expand Down Expand Up @@ -552,13 +555,12 @@ Observable<GenericOverall> setChatReadMarker(@Header("Authorization") String aut
@GET
Observable<RoomsOverall> getOpenConversations(@Header("Authorization") String authorization, @Url String url);


/*
* OCS Status API
*/
@GET
Observable<StatusOverall> status(@Header("Authorization") String authorization, @Url String url);

@GET
Observable<StatusOverall> backupStatus(@Header("Authorization") String authorization, @Url String url);

@GET
Observable<ResponseBody> getPredefinedStatuses(@Header("Authorization") String authorization, @Url String url);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import kotlinx.serialization.Serializable
data class UserStatusCapability(
@JsonField(name = ["enabled"])
var enabled: Boolean,
@JsonField(name = ["restore"])
var restore: Boolean,
@JsonField(name = ["supports_emoji"])
var supportsEmoji: Boolean
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(false, false)
constructor() : this(false, false, false)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ data class PredefinedStatus(
@JsonField(name = ["id"])
var id: String,
@JsonField(name = ["icon"])
var icon: String,
var icon: String?,
@JsonField(name = ["message"])
var message: String,
@JsonField(name = ["clearAt"])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ import com.nextcloud.talk.databinding.DialogSetStatusBinding
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.status.ClearAt
import com.nextcloud.talk.models.json.status.Status
import com.nextcloud.talk.models.json.status.StatusOverall
import com.nextcloud.talk.models.json.status.StatusType
import com.nextcloud.talk.models.json.status.predefined.PredefinedStatus
import com.nextcloud.talk.models.json.status.predefined.PredefinedStatusOverall
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.CapabilitiesUtil.isRestoreStatusAvailable
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.vanniktech.emoji.EmojiPopup
Expand All @@ -54,6 +56,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import retrofit2.HttpException
import java.util.Calendar
import java.util.Locale
import javax.inject.Inject
Expand Down Expand Up @@ -86,12 +89,16 @@ class SetStatusDialogFragment :

private var currentUser: User? = null
private var currentStatus: Status? = null
private lateinit var backupStatus: Status

val predefinedStatusesList = ArrayList<PredefinedStatus>()

private val disposables: MutableList<Disposable> = ArrayList()

private lateinit var adapter: PredefinedStatusListAdapter
private var clearAt: Long? = null
private lateinit var popup: EmojiPopup
private var isBackupStatusAvailable = false

@Inject
lateinit var ncApi: NcApi
Expand All @@ -114,42 +121,88 @@ class SetStatusDialogFragment :
currentStatus = it.getParcelable(ARG_CURRENT_STATUS_PARAM)

credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)!!
ncApi.getPredefinedStatuses(credentials, ApiUtils.getUrlForPredefinedStatuses(currentUser?.baseUrl!!))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<ResponseBody> {
if (isRestoreStatusAvailable(currentUser!!)) {
checkBackupStatus()
}
fetchPredefinedStatuses()
}
}

override fun onSubscribe(d: Disposable) {
// unused atm
private fun fetchPredefinedStatuses() {
ncApi.getPredefinedStatuses(credentials, ApiUtils.getUrlForPredefinedStatuses(currentUser?.baseUrl!!))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<ResponseBody> {
override fun onSubscribe(d: Disposable) {
disposables.add(d)
}

@SuppressLint("NotifyDataSetChanged")
override fun onNext(responseBody: ResponseBody) {
val predefinedStatusOverall: PredefinedStatusOverall = LoganSquare.parse(
responseBody.string(),
PredefinedStatusOverall::class.java
)
predefinedStatusOverall.ocs?.data?.let { predefinedStatusesList.addAll(it) }

if (currentStatus?.messageIsPredefined == true && currentStatus?.messageId?.isNotEmpty() == true) {
val messageId = currentStatus!!.messageId
selectedPredefinedStatus = predefinedStatusesList.firstOrNull { ps -> messageId == ps.id }
}

override fun onNext(responseBody: ResponseBody) {
val predefinedStatusOverall: PredefinedStatusOverall = LoganSquare.parse(
responseBody
.string(),
PredefinedStatusOverall::class.java
)
predefinedStatusOverall.ocs?.data?.let { it1 -> predefinedStatusesList.addAll(it1) }
adapter.notifyDataSetChanged()
}

if (currentStatus?.messageIsPredefined == true &&
currentStatus?.messageId?.isNotEmpty() == true
) {
val messageId = currentStatus!!.messageId
selectedPredefinedStatus = predefinedStatusesList.firstOrNull { ps -> messageId == ps.id }
}
override fun onError(e: Throwable) {
Log.e(TAG, "Error while fetching predefined statuses", e)
}

override fun onComplete() {
// unused atm
}
})
}

private fun checkBackupStatus() {
ncApi.backupStatus(credentials, ApiUtils.getUrlForBackupStatus(currentUser?.baseUrl!!, currentUser?.userId!!))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<StatusOverall> {

override fun onSubscribe(d: Disposable) {
disposables.add(d)
}

@SuppressLint("NotifyDataSetChanged")
override fun onNext(statusOverall: StatusOverall) {
if (statusOverall.ocs?.meta?.statusCode == HTTP_STATUS_CODE_OK) {
backupStatus = statusOverall.ocs?.data!!
isBackupStatusAvailable = true
val backupPredefinedStatus = PredefinedStatus(
backupStatus.userId!!,
backupStatus.icon,
backupStatus.message!!,
ClearAt(type = "period", time = backupStatus.clearAt.toString())
)
binding.automaticStatus.visibility = View.VISIBLE
adapter.isBackupStatusAvailable = true
predefinedStatusesList.add(0, backupPredefinedStatus)
adapter.notifyDataSetChanged()
}
}

override fun onError(e: Throwable) {
Log.e(TAG, "Error while fetching predefined statuses", e)
override fun onError(e: Throwable) {
if (e is HttpException && e.code() == HTTP_STATUS_CODE_NOT_FOUND) {
Log.d(TAG, "User does not have a backup status set")
} else {
Log.e(TAG, "Error while getting user backup status", e)
}
}

override fun onComplete() {
// unused atm
}
})
}
override fun onComplete() {
// unused atm
}
})
}

@SuppressLint("InflateParams")
Expand All @@ -168,7 +221,7 @@ class SetStatusDialogFragment :

setupCurrentStatus()

adapter = PredefinedStatusListAdapter(this, requireContext())
adapter = PredefinedStatusListAdapter(this, requireContext(), isBackupStatusAvailable)
adapter.list = predefinedStatusesList

binding.predefinedStatusList.adapter = adapter
Expand All @@ -183,7 +236,6 @@ class SetStatusDialogFragment :
binding.clearStatus.setOnClickListener { clearStatus() }
binding.setStatus.setOnClickListener { setStatusMessage() }
binding.emoji.setOnClickListener { openEmojiPopup() }

popup = EmojiPopup(
rootView = view,
editText = binding.emoji,
Expand Down Expand Up @@ -244,6 +296,44 @@ class SetStatusDialogFragment :
}
}

override fun revertStatus() {
if (isRestoreStatusAvailable(currentUser!!)) {
ncApi.revertStatus(
credentials,
ApiUtils.getUrlForRevertStatus(currentUser?.baseUrl!!, currentStatus?.messageId)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<GenericOverall> {

override fun onSubscribe(d: Disposable) {
disposables.add(d)
}

@SuppressLint("NotifyDataSetChanged")
override fun onNext(genericOverall: GenericOverall) {
Log.d(TAG, "$genericOverall")
if (genericOverall.ocs?.meta?.statusCode == HTTP_STATUS_CODE_OK) {
binding.automaticStatus.visibility = View.GONE
adapter.isBackupStatusAvailable = false
predefinedStatusesList.removeAt(0)
adapter.notifyDataSetChanged()
currentStatus = backupStatus
setupCurrentStatus()
dismiss()
}
}
override fun onError(e: Throwable) {
Log.e(TAG, "Failed to revert user status", e)
}

override fun onComplete() {
// unused atm
}
})
}
}

private fun setupGeneralStatusOptions() {
binding.onlineStatus.setOnClickListener { setStatus(StatusType.ONLINE) }
binding.dndStatus.setOnClickListener { setStatus(StatusType.DND) }
Expand Down Expand Up @@ -357,7 +447,7 @@ class SetStatusDialogFragment :
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(object : Observer<GenericOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
disposables.add(d)
}

override fun onNext(statusOverall: GenericOverall) {
Expand All @@ -384,7 +474,7 @@ class SetStatusDialogFragment :
)
.observeOn(AndroidSchedulers.mainThread()).subscribe(object : Observer<GenericOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
disposables.add(d)
}

override fun onNext(statusOverall: GenericOverall) {
Expand Down Expand Up @@ -487,7 +577,9 @@ class SetStatusDialogFragment :
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())?.subscribe(object : Observer<GenericOverall> {
override fun onSubscribe(d: Disposable) = Unit
override fun onSubscribe(d: Disposable) {
disposables.add(d)
}

override fun onNext(t: GenericOverall) {
Log.d(TAG, "PredefinedStatusMessage successfully set")
Expand All @@ -498,7 +590,9 @@ class SetStatusDialogFragment :
Log.e(TAG, "failed to set PredefinedStatusMessage", e)
}

override fun onComplete() = Unit
override fun onComplete() {
// unused atm
}
})
}
}
Expand Down Expand Up @@ -544,11 +638,26 @@ class SetStatusDialogFragment :
}
}

private fun dispose() {
for (i in disposables.indices) {
if (!disposables[i].isDisposed) {
disposables[i].dispose()
}
}
}

override fun onDestroy() {
dispose()
super.onDestroy()
}

/**
* Fragment creator
*/
companion object {
private val TAG = SetStatusDialogFragment::class.simpleName
private const val HTTP_STATUS_CODE_OK = 200
private const val HTTP_STATUS_CODE_NOT_FOUND = 404

@JvmStatic
fun newInstance(status: Status): SetStatusDialogFragment {
Expand Down
Loading

0 comments on commit b361d56

Please sign in to comment.