diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index a038b357c..6dad5fefc 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -40,6 +40,7 @@ import com.nextcloud.talk.models.json.participants.AddParticipantOverall; import com.nextcloud.talk.models.json.participants.ParticipantsOverall; import com.nextcloud.talk.models.json.push.PushRegistrationOverall; import com.nextcloud.talk.models.json.reactions.ReactionsOverall; +import com.nextcloud.talk.models.json.reminder.ReminderOverall; import com.nextcloud.talk.models.json.search.ContactsByNumberOverall; import com.nextcloud.talk.models.json.signaling.SignalingOverall; import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall; @@ -671,4 +672,18 @@ public interface NcApi { @Query("text") String text, @Query("toLanguage") String toLanguage, @Nullable @Query("fromLanguage") String fromLanguage); + + @GET + Observable getReminder(@Header("Authorization") String authorization, + @Url String url); + + @DELETE + Observable deleteReminder(@Header("Authorization") String authorization, + @Url String url); + + @FormUrlEncoded + @POST + Observable setReminder(@Header("Authorization") String authorization, + @Url String url, + @Field("timestamp") int timestamp); } diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 8acd7ef1c..45b9a1dc8 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -95,6 +95,7 @@ import androidx.core.text.bold import androidx.core.widget.doAfterTextChanged import androidx.emoji2.text.EmojiCompat import androidx.emoji2.widget.EmojiTextView +import androidx.fragment.app.DialogFragment import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager @@ -183,6 +184,7 @@ import com.nextcloud.talk.ui.MicInputCloud import com.nextcloud.talk.ui.StatusDrawable import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet import com.nextcloud.talk.ui.dialog.AttachmentDialog +import com.nextcloud.talk.ui.dialog.DateTimePickerFragment import com.nextcloud.talk.ui.dialog.MessageActionsDialog import com.nextcloud.talk.ui.dialog.ShowReactionsDialog import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions @@ -3860,6 +3862,16 @@ class ChatActivity : startActivity(intent) } + fun remindMeLater(message: ChatMessage?) { + Log.d(TAG, "remindMeLater called") + val newFragment: DialogFragment = DateTimePickerFragment.newInstance( + roomToken, + message!!.id, + chatViewModel + ) + newFragment.show(supportFragmentManager, DateTimePickerFragment.TAG) + } + fun markAsUnread(message: IMessage?) { val chatMessage = message as ChatMessage? if (chatMessage!!.previousMessageId > NO_PREVIOUS_MESSAGE_ID) { diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt index da39d25ff..7c85caa15 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt @@ -22,10 +22,14 @@ package com.nextcloud.talk.chat.data import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.models.domain.ConversationModel - +import com.nextcloud.talk.models.json.generic.GenericOverall +import com.nextcloud.talk.models.json.reminder.Reminder import io.reactivex.Observable interface ChatRepository { fun getRoom(user: User, roomToken: String): Observable fun joinRoom(user: User, roomToken: String, roomPassword: String): Observable + fun setReminder(user: User, roomToken: String, messageId: String, timeStamp: Int): Observable + fun getReminder(user: User, roomToken: String, messageId: String): Observable + fun deleteReminder(user: User, roomToken: String, messageId: String): Observable } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepositoryImpl.kt index 51f77746e..81327cf74 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepositoryImpl.kt @@ -23,6 +23,8 @@ package com.nextcloud.talk.chat.data import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.models.domain.ConversationModel +import com.nextcloud.talk.models.json.generic.GenericOverall +import com.nextcloud.talk.models.json.reminder.Reminder import com.nextcloud.talk.utils.ApiUtils import io.reactivex.Observable @@ -54,4 +56,38 @@ class ChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository { roomPassword ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) } } + + override fun setReminder(user: User, roomToken: String, messageId: String, timeStamp: Int): Observable { + val credentials: String = ApiUtils.getCredentials(user.username, user.token) + val apiVersion = ApiUtils.getChatApiVersion(user, intArrayOf(ApiUtils.APIv1, 1)) + return ncApi.setReminder( + credentials, + ApiUtils.getUrlForReminder(user, roomToken, messageId, apiVersion), + timeStamp + ).map { + it.ocs!!.data + } + } + + override fun getReminder(user: User, roomToken: String, messageId: String): Observable { + val credentials: String = ApiUtils.getCredentials(user.username, user.token) + val apiVersion = ApiUtils.getChatApiVersion(user, intArrayOf(ApiUtils.APIv1, 1)) + return ncApi.getReminder( + credentials, + ApiUtils.getUrlForReminder(user, roomToken, messageId, apiVersion) + ).map { + it.ocs!!.data + } + } + + override fun deleteReminder(user: User, roomToken: String, messageId: String): Observable { + val credentials: String = ApiUtils.getCredentials(user.username, user.token) + val apiVersion = ApiUtils.getChatApiVersion(user, intArrayOf(ApiUtils.APIv1, 1)) + return ncApi.deleteReminder( + credentials, + ApiUtils.getUrlForReminder(user, roomToken, messageId, apiVersion) + ).map { + it + } + } } diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt index 5ab69fb67..09074cab2 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt @@ -27,6 +27,8 @@ import androidx.lifecycle.ViewModel import com.nextcloud.talk.chat.data.ChatRepository import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.models.domain.ConversationModel +import com.nextcloud.talk.models.json.generic.GenericOverall +import com.nextcloud.talk.models.json.reminder.Reminder import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable @@ -40,6 +42,13 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository) object GetRoomStartState : ViewState object GetRoomErrorState : ViewState + object GetReminderStartState : ViewState + open class GetReminderExistState(val reminder: Reminder) : ViewState + + private val _getReminderExistState: MutableLiveData = MutableLiveData(GetReminderStartState) + val getReminderExistState: LiveData + get() = _getReminderExistState + open class GetRoomSuccessState(val conversationModel: ConversationModel) : ViewState private val _getRoomViewState: MutableLiveData = MutableLiveData(GetRoomStartState) @@ -71,6 +80,43 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository) ?.subscribe(JoinRoomObserver()) } + fun setReminder(user: User, roomToken: String, messageId: String, timestamp: Int) { + repository.setReminder(user, roomToken, messageId, timestamp) + .subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(SetReminderObserver()) + } + + fun getReminder(user: User, roomToken: String, messageId: String) { + repository.getReminder(user, roomToken, messageId) + .subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(GetReminderObserver()) + } + + fun deleteReminder(user: User, roomToken: String, messageId: String) { + repository.deleteReminder(user, roomToken, messageId) + .subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + // unused atm + } + + override fun onNext(genericOverall: GenericOverall) { + _getReminderExistState.value = GetReminderStartState + } + + override fun onError(e: Throwable) { + Log.d(TAG, "Error when deleting reminder $e") + } + + override fun onComplete() { + // unused atm + } + }) + } + inner class GetRoomObserver : Observer { override fun onSubscribe(d: Disposable) { // unused atm @@ -109,6 +155,43 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository) } } + inner class SetReminderObserver : Observer { + override fun onSubscribe(d: Disposable) { + // unused atm + } + + override fun onNext(reminder: Reminder) { + Log.d(TAG, "reminder set successfully") + } + + override fun onError(e: Throwable) { + Log.e(TAG, "Error when sending reminder, $e") + } + + override fun onComplete() { + // unused atm + } + } + + inner class GetReminderObserver : Observer { + override fun onSubscribe(d: Disposable) { + // unused atm + } + + override fun onNext(reminder: Reminder) { + _getReminderExistState.value = GetReminderExistState(reminder) + } + + override fun onError(e: Throwable) { + Log.d(TAG, "Error when getting reminder $e") + _getReminderExistState.value = GetReminderStartState + } + + override fun onComplete() { + // unused atm + } + } + companion object { private val TAG = ChatViewModel::class.simpleName const val JOIN_ROOM_RETRY_COUNT: Long = 3 diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt index 59a5a441a..9c4cfcae9 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt @@ -175,7 +175,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor } else if (isSpreedNotification()) { Log.d(TAG, "pushMessage.type: " + pushMessage.type) when (pushMessage.type) { - TYPE_CHAT, TYPE_ROOM, TYPE_RECORDING -> handleNonCallPushMessage() + TYPE_CHAT, TYPE_ROOM, TYPE_RECORDING, TYPE_REMINDER -> handleNonCallPushMessage() TYPE_CALL -> handleCallPushMessage() else -> Log.e(TAG, "unknown pushMessage.type") } @@ -407,7 +407,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor ) { var category = "" when (pushMessage.type) { - TYPE_CHAT, TYPE_ROOM, TYPE_RECORDING -> category = Notification.CATEGORY_MESSAGE + TYPE_CHAT, TYPE_ROOM, TYPE_RECORDING, TYPE_REMINDER -> category = Notification.CATEGORY_MESSAGE TYPE_CALL -> category = Notification.CATEGORY_CALL else -> Log.e(TAG, "unknown pushMessage.type") } @@ -464,7 +464,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { when (pushMessage.type) { - TYPE_CHAT, TYPE_ROOM, TYPE_RECORDING -> { + TYPE_CHAT, TYPE_ROOM, TYPE_RECORDING, TYPE_REMINDER -> { notificationBuilder.setChannelId( NotificationUtils.NotificationChannels.NOTIFICATION_CHANNEL_MESSAGES_V4.name ) @@ -489,7 +489,9 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor val systemNotificationId: Int = activeStatusBarNotification?.id ?: calculateCRC32(System.currentTimeMillis().toString()).toInt() - if (TYPE_CHAT == pushMessage.type && pushMessage.notificationUser != null) { + if ((TYPE_CHAT == pushMessage.type || TYPE_REMINDER == pushMessage.type) && + pushMessage.notificationUser != null + ) { prepareChatNotification(notificationBuilder, activeStatusBarNotification, systemNotificationId) addReplyAction(notificationBuilder, systemNotificationId) addMarkAsReadAction(notificationBuilder, systemNotificationId) @@ -522,6 +524,8 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor else -> // assuming one2one largeIcon = if (TYPE_CHAT == pushMessage.type || TYPE_ROOM == pushMessage.type) { ContextCompat.getDrawable(context!!, R.drawable.ic_comment)?.toBitmap()!! + } else if (TYPE_REMINDER == pushMessage.type) { + ContextCompat.getDrawable(context!!, R.drawable.ic_timer_black_24dp)?.toBitmap()!! } else { ContextCompat.getDrawable(context!!, R.drawable.ic_call_black_24dp)?.toBitmap()!! } @@ -984,6 +988,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor private const val TYPE_ROOM = "room" private const val TYPE_CALL = "call" private const val TYPE_RECORDING = "recording" + private const val TYPE_REMINDER = "reminder" private const val SPREED_APP = "spreed" private const val TIMER_START = 1 private const val TIMER_COUNT = 12 diff --git a/app/src/main/java/com/nextcloud/talk/models/json/reminder/Reminder.kt b/app/src/main/java/com/nextcloud/talk/models/json/reminder/Reminder.kt new file mode 100644 index 000000000..b8b47fc28 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/reminder/Reminder.kt @@ -0,0 +1,41 @@ +/* + * Nextcloud Talk application + * + * @author Julius Linus + * Copyright (C) 2023 Julius Linus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.models.json.reminder + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.parcelize.Parcelize + +@Parcelize +@JsonObject +data class Reminder( + @JsonField(name = ["userid"]) + var userid: String? = null, + @JsonField(name = ["token"]) + var token: String? = null, + @JsonField(name = ["messageId"]) + var messageId: Int? = null, + @JsonField(name = ["timestamp"]) + var timestamp: Int? = null +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null, null, null, null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/reminder/ReminderOCS.kt b/app/src/main/java/com/nextcloud/talk/models/json/reminder/ReminderOCS.kt new file mode 100644 index 000000000..2dbe0309b --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/reminder/ReminderOCS.kt @@ -0,0 +1,38 @@ +/* + * Nextcloud Talk application + * + * @author Julius Linus + * Copyright (C) 2023 Julius Linus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.models.json.reminder + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import com.nextcloud.talk.models.json.generic.GenericMeta +import kotlinx.parcelize.Parcelize + +@Parcelize +@JsonObject +data class ReminderOCS( + @JsonField(name = ["meta"]) + var meta: GenericMeta? = null, + @JsonField(name = ["data"]) + var data: Reminder? = null +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null, null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/reminder/ReminderOverall.kt b/app/src/main/java/com/nextcloud/talk/models/json/reminder/ReminderOverall.kt new file mode 100644 index 000000000..8c94cf867 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/reminder/ReminderOverall.kt @@ -0,0 +1,35 @@ +/* + * Nextcloud Talk application + * + * @author Julius Linus + * Copyright (C) 2023 Julius Linus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.models.json.reminder + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.parcelize.Parcelize + +@Parcelize +@JsonObject +data class ReminderOverall( + @JsonField(name = ["ocs"]) + var ocs: ReminderOCS? = null +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null) +} diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/DateTimePickerFragment.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/DateTimePickerFragment.kt new file mode 100644 index 000000000..bc9e256b6 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/DateTimePickerFragment.kt @@ -0,0 +1,309 @@ +/* + * Nextcloud Talk application + * + * @author Julius Linus + * Copyright (C) 2023 Julius Linus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.ui.dialog + +import android.app.Dialog +import android.os.Bundle +import android.text.format.DateUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import autodagger.AutoInjector +import com.google.android.material.datepicker.CalendarConstraints +import com.google.android.material.datepicker.DateValidatorPointForward +import com.google.android.material.datepicker.MaterialDatePicker +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.timepicker.MaterialTimePicker +import com.nextcloud.android.common.ui.theme.utils.ColorRole +import com.nextcloud.talk.R +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.chat.viewmodels.ChatViewModel +import com.nextcloud.talk.databinding.DialogDateTimePickerBinding +import com.nextcloud.talk.ui.theme.ViewThemeUtils +import com.nextcloud.talk.users.UserManager +import java.util.Calendar +import java.util.TimeZone +import javax.inject.Inject + +@Suppress("TooManyFunctions") +@AutoInjector(NextcloudTalkApplication::class) +class DateTimePickerFragment( + token: String, + id: String, + chatViewModel: ChatViewModel +) : DialogFragment() { + lateinit var binding: DialogDateTimePickerBinding + private var dialogView: View? = null + private var viewModel = chatViewModel + private var currentTimeStamp: Long? = null + private var roomToken = token + private var messageId = id + private var laterTodayTimeStamp = 0L + private var tomorrowTimeStamp = 0L + private var weekendTimeStamp = 0L + private var nextWeekTimeStamp = 0L + + @Inject + lateinit var userManager: UserManager + + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + binding = DialogDateTimePickerBinding.inflate(LayoutInflater.from(context)) + dialogView = binding.root + return MaterialAlertDialogBuilder(requireContext()).setView(dialogView).create() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) + setUpDefaults() + setUpColors() + setListeners() + getReminder() + viewModel.getReminderExistState.observe(this) { state -> + when (state) { + is ChatViewModel.GetReminderExistState -> { + val timeStamp = state.reminder.timestamp?.toLong()?.times(ONE_SEC) + showDelete(true) + setTimeStamp(getTimeFromTimeStamp(timeStamp!!)) + } + + else -> { + showDelete(false) + binding.dateTimePickerTimestamp.text = "" + } + } + } + + return inflater.inflate(R.layout.dialog_date_time_picker, container, false) + } + + private fun setUpDefaults() { + val currTime = getTimeFromCalendar() + val currentWeekInYear = Calendar.getInstance().get(Calendar.WEEK_OF_YEAR) + + laterTodayTimeStamp = getTimeFromCalendar(hour = HOUR_SIX_PM, minute = 0) + binding.dateTimePickerLaterTodayTextview.text = getTimeFromTimeStamp(laterTodayTimeStamp) + + if (Calendar.getInstance().get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) { + tomorrowTimeStamp = getTimeFromCalendar( + hour = HOUR_EIGHT_AM, + minute = 0, + daysToAdd = 1, + weekInYear = + currentWeekInYear + 1 + ) + + binding.dateTimePickerWeekend.visibility = View.GONE // because today is the weekend + } else { + tomorrowTimeStamp = getTimeFromCalendar(hour = HOUR_EIGHT_AM, minute = 0, daysToAdd = 1) + weekendTimeStamp = getTimeFromCalendar(hour = HOUR_EIGHT_AM, day = Calendar.SATURDAY, minute = 0) + } + binding.dateTimePickerTomorrowTextview.text = getTimeFromTimeStamp(tomorrowTimeStamp) + binding.dateTimePickerWeekendTextview.text = getTimeFromTimeStamp(weekendTimeStamp) + + nextWeekTimeStamp = getTimeFromCalendar( + hour = HOUR_EIGHT_AM, + day = Calendar.MONDAY, + minute = 0, + weekInYear = + currentWeekInYear + 1 + ) // this should only pick mondays from next week only + binding.dateTimePickerNextWeekTextview.text = getTimeFromTimeStamp(nextWeekTimeStamp) + + // This is to hide the later today option, if it's past 6pm + if (currTime > laterTodayTimeStamp) { + binding.dateTimePickerLaterToday.visibility = View.GONE + } + + // This is to hide the tomorrow option, if that's also the weekend + if (binding.dateTimePickerTomorrowTextview.text == binding.dateTimePickerWeekendTextview.text) { + binding.dateTimePickerTomorrow.visibility = View.GONE + } + } + + private fun getReminder() { + viewModel.getReminder(userManager.currentUser.blockingGet(), roomToken, messageId) + } + + private fun showDelete(value: Boolean) { + if (value) { + binding.buttonDelete.visibility = View.VISIBLE + } else { + binding.buttonDelete.visibility = View.GONE + } + } + + private fun setUpColors() { + binding.root.let { + viewThemeUtils.platform.colorViewBackground(it) + } + + binding.dateTimePickerCustomIcon.let { + viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY) + } + + binding.dateTimePickerTimestamp.let { + viewThemeUtils.material.themeSearchBarText(it) + } + + binding.run { + listOf( + binding.buttonClose, + binding.buttonSet + ) + }.forEach(viewThemeUtils.material::colorMaterialButtonPrimaryBorderless) + } + + private fun setListeners() { + binding.dateTimePickerLaterToday.setOnClickListener { + currentTimeStamp = laterTodayTimeStamp / ONE_SEC + setTimeStamp(getTimeFromTimeStamp(laterTodayTimeStamp)) + } + binding.dateTimePickerTomorrow.setOnClickListener { + currentTimeStamp = tomorrowTimeStamp / ONE_SEC + setTimeStamp(getTimeFromTimeStamp(tomorrowTimeStamp)) + } + binding.dateTimePickerWeekend.setOnClickListener { + currentTimeStamp = weekendTimeStamp / ONE_SEC + setTimeStamp(getTimeFromTimeStamp(weekendTimeStamp)) + } + binding.dateTimePickerNextWeek.setOnClickListener { + currentTimeStamp = nextWeekTimeStamp / ONE_SEC + setTimeStamp(getTimeFromTimeStamp(nextWeekTimeStamp)) + } + binding.dateTimePickerCustom.setOnClickListener { + val constraintsBuilder = CalendarConstraints.Builder() + .setValidator(DateValidatorPointForward.now()) + .build() + val time = System.currentTimeMillis() + val datePicker = MaterialDatePicker.Builder.datePicker() + .setTitleText(R.string.nc_remind) + .setSelection(time + TimeZone.getDefault().getOffset(time)) + .setCalendarConstraints(constraintsBuilder).build() + + datePicker.addOnPositiveButtonClickListener { selection -> + val localTimeInMillis = selection - TimeZone.getDefault().getOffset(selection) + val calendar = Calendar.getInstance() + calendar.timeInMillis = localTimeInMillis + + val year = calendar.get(Calendar.YEAR) + val month = calendar.get(Calendar.MONTH) + val day = calendar.get(Calendar.DAY_OF_WEEK) + val weekInYear = calendar.get(Calendar.WEEK_OF_YEAR) + + setUpTimePicker(year, month, day, weekInYear) + } + datePicker.show(this.parentFragmentManager, TAG) + } + + binding.buttonClose.setOnClickListener { dismiss() } + binding.buttonSet.setOnClickListener { + currentTimeStamp?.let { time -> + viewModel.setReminder(userManager.currentUser.blockingGet(), roomToken, messageId, time.toInt()) + } + dismiss() + } + binding.buttonDelete.setOnClickListener { + viewModel.deleteReminder(userManager.currentUser.blockingGet(), roomToken, messageId) + } + } + + private fun setUpTimePicker(year: Int, month: Int, day: Int, weekInYear: Int) { + val timePicker = MaterialTimePicker + .Builder() + .setTitleText(R.string.nc_remind) + .build() + + timePicker.addOnPositiveButtonClickListener { + val timestamp = getTimeFromCalendar( + year, + month, + day, + timePicker.hour, + timePicker.minute, + weekInYear = weekInYear + ) + setTimeStamp(getTimeFromTimeStamp(timestamp)) + currentTimeStamp = timestamp / ONE_SEC + } + + timePicker.show(this.parentFragmentManager, TAG) + } + + @Suppress("LongParameterList") + private fun getTimeFromCalendar( + year: Int = Calendar.getInstance().get(Calendar.YEAR), + month: Int = Calendar.getInstance().get(Calendar.MONTH), + day: Int = Calendar.getInstance().get(Calendar.DAY_OF_WEEK), + hour: Int = Calendar.getInstance().get(Calendar.HOUR_OF_DAY), + minute: Int = Calendar.getInstance().get(Calendar.MINUTE), + daysToAdd: Int = 0, + weekInYear: Int = Calendar.getInstance().get(Calendar.WEEK_OF_YEAR) + ): Long { + val calendar: Calendar = Calendar.getInstance().apply { + set(Calendar.YEAR, year) + set(Calendar.MONTH, month) + set(Calendar.DAY_OF_WEEK, day) + add(Calendar.DAY_OF_WEEK, daysToAdd) + set(Calendar.WEEK_OF_YEAR, weekInYear) + set(Calendar.HOUR_OF_DAY, hour) + set(Calendar.MINUTE, minute) + set(Calendar.SECOND, 0) + } + return calendar.timeInMillis + } + + private fun setTimeStamp(date: String) { + binding.dateTimePickerTimestamp.text = date + } + + private fun getTimeFromTimeStamp(time: Long): String { + return DateUtils.formatDateTime( + requireContext(), + time, + DateUtils.FORMAT_SHOW_DATE + ) + ", " + DateUtils.formatDateTime( + requireContext(), + time, + DateUtils.FORMAT_SHOW_TIME + ) + } + + companion object { + val TAG = DateTimePickerFragment::class.simpleName + private const val ONE_SEC = 1000 + private const val HOUR_EIGHT_AM = 8 + private const val HOUR_SIX_PM = 18 + + @JvmStatic + fun newInstance( + token: String, + id: String, + chatViewModel: ChatViewModel + ) = DateTimePickerFragment( + token, + id, + chatViewModel + ) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt index 2ac437814..dc95b210a 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt @@ -109,6 +109,7 @@ class MessageActionsDialog( ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() && !(message.isDeletedCommentMessage || message.isDeleted) ) + initMenuRemindMessage(!message.isDeleted && CapabilitiesUtilNew.isRemindSupported(user)) initMenuMarkAsUnread( message.previousMessageId > NO_PREVIOUS_MESSAGE_ID && ChatMessage.MessageType.SYSTEM_MESSAGE != message.getCalculateMessageType() @@ -264,6 +265,17 @@ class MessageActionsDialog( dialogMessageActionsBinding.menuForwardMessage.visibility = getVisibility(visible) } + private fun initMenuRemindMessage(visible: Boolean) { + if (visible) { + dialogMessageActionsBinding.menuNotifyMessage.setOnClickListener { + chatActivity.remindMeLater(message) + dismiss() + } + } + + dialogMessageActionsBinding.menuNotifyMessage.visibility = getVisibility(visible) + } + private fun initMenuDeleteMessage(visible: Boolean) { if (visible) { dialogMessageActionsBinding.menuDeleteMessage.setOnClickListener { diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java index 7c209e574..4b0ec13d9 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java @@ -530,4 +530,9 @@ public class ApiUtils { public static String getUrlForTranslation(String baseUrl) { return baseUrl + ocsApiVersion + "/translation/translate"; } + + public static String getUrlForReminder(User user, String roomToken, String messageId, int version) { + String url = ApiUtils.getUrlForChatMessage(version, user.getBaseUrl(), roomToken, messageId); + return url + "/reminder"; + } } diff --git a/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt b/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt index 96915cc51..5816fd356 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt @@ -145,7 +145,7 @@ object CapabilitiesUtilNew { } @JvmStatic - fun getAttachmentFolder(user: User): String? { + fun getAttachmentFolder(user: User): String { if (user.capabilities?.spreedCapability?.config?.containsKey("attachments") == true) { val map = user.capabilities!!.spreedCapability!!.config!!["attachments"] if (map?.containsKey("folder") == true) { @@ -241,5 +241,14 @@ object CapabilitiesUtilNew { } } + fun isRemindSupported(user: User?): Boolean { + if (user?.capabilities != null) { + val capabilities = user.capabilities + return capabilities?.spreedCapability?.features?.contains("remind-me-later") == true + } + + return false + } + const val DEFAULT_CHAT_SIZE = 1000 } diff --git a/app/src/main/res/drawable/baseline_calendar_month_24.xml b/app/src/main/res/drawable/baseline_calendar_month_24.xml new file mode 100644 index 000000000..692515653 --- /dev/null +++ b/app/src/main/res/drawable/baseline_calendar_month_24.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/app/src/main/res/layout/dialog_date_time_picker.xml b/app/src/main/res/layout/dialog_date_time_picker.xml new file mode 100644 index 000000000..2fd1e51a6 --- /dev/null +++ b/app/src/main/res/layout/dialog_date_time_picker.xml @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_message_actions.xml b/app/src/main/res/layout/dialog_message_actions.xml index 9610f8048..b1f2042bd 100644 --- a/app/src/main/res/layout/dialog_message_actions.xml +++ b/app/src/main/res/layout/dialog_message_actions.xml @@ -221,6 +221,39 @@ + + + + + + + + 1080 This is a test message Lock recording for continuously recording of the voice message + Remind me later + Next week + This weekend + Tomorrow + Later today + Custom + Set + Calendar