diff --git a/app/build.gradle b/app/build.gradle index ba4d95e13..66b70f659 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -140,7 +140,7 @@ android { ext { androidxCameraVersion = "1.2.3" coilKtVersion = "2.4.0" - daggerVersion = "2.47" + daggerVersion = "2.48" emojiVersion = "1.3.0" lifecycleVersion = '2.6.1' okhttpVersion = "4.11.0" @@ -269,7 +269,7 @@ dependencies { implementation "androidx.media3:media3-ui:$media3_version" implementation 'com.github.chrisbanes:PhotoView:2.3.0' - implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.27' + implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.28' implementation "io.noties.markwon:core:$markwonVersion" implementation "io.noties.markwon:ext-strikethrough:$markwonVersion" diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageInterface.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageInterface.kt new file mode 100644 index 000000000..8b4ce5bc5 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageInterface.kt @@ -0,0 +1,8 @@ +package com.nextcloud.talk.adapters.messages + +import com.nextcloud.talk.models.json.chat.ChatMessage + +interface SystemMessageInterface { + fun expandSystemMessage(chatMessage: ChatMessage) + fun collapseSystemMessages() +} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageViewHolder.java deleted file mode 100644 index 1dc095e09..000000000 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageViewHolder.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * 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.adapters.messages; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.text.Spannable; -import android.text.SpannableString; -import android.view.View; -import android.view.ViewGroup; - -import com.nextcloud.talk.R; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.models.json.chat.ChatMessage; -import com.nextcloud.talk.utils.DateUtils; -import com.nextcloud.talk.utils.DisplayUtils; -import com.nextcloud.talk.utils.preferences.AppPreferences; -import com.stfalcon.chatkit.messages.MessageHolders; - -import java.util.Map; - -import javax.inject.Inject; - -import androidx.core.view.ViewCompat; -import autodagger.AutoInjector; - -@AutoInjector(NextcloudTalkApplication.class) -public class SystemMessageViewHolder extends MessageHolders.IncomingTextMessageViewHolder { - - @Inject - AppPreferences appPreferences; - - @Inject - Context context; - - @Inject - DateUtils dateUtils; - - protected ViewGroup background; - - public SystemMessageViewHolder(View itemView) { - super(itemView); - NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this); - background = itemView.findViewById(R.id.container); - } - - @Override - public void onBind(ChatMessage message) { - super.onBind(message); - - Resources resources = itemView.getResources(); - int pressedColor; - int mentionColor; - - pressedColor = resources.getColor(R.color.bg_message_list_incoming_bubble); - mentionColor = resources.getColor(R.color.textColorMaxContrast); - - Drawable bubbleDrawable = DisplayUtils.getMessageSelector(resources.getColor(R.color.transparent), - resources.getColor(R.color.transparent), - pressedColor, - R.drawable.shape_grouped_incoming_message); - ViewCompat.setBackground(background, bubbleDrawable); - - Spannable messageString = new SpannableString(message.getText()); - - if (message.getMessageParameters() != null && message.getMessageParameters().size() > 0) { - for (String key : message.getMessageParameters().keySet()) { - Map individualMap = message.getMessageParameters().get(key); - - if (individualMap != null && individualMap.containsKey("name")) { - String searchText; - if ("user".equals(individualMap.get("type")) || - "guest".equals(individualMap.get("type")) || - "call".equals(individualMap.get("type")) - ) { - searchText = "@" + individualMap.get("name"); - } else { - searchText = individualMap.get("name"); - } - messageString = DisplayUtils.searchAndColor(messageString, searchText, mentionColor); - } - } - } - - text.setText(messageString); - - if (time != null) { - time.setText(dateUtils.getLocalTimeStringFromTimestamp(message.getTimestamp())); - } - - itemView.setTag(R.string.replyable_message_view_tag, message.getReplyable()); - } -} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageViewHolder.kt new file mode 100644 index 000000000..0c1feeba8 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageViewHolder.kt @@ -0,0 +1,150 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * 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.adapters.messages + +import android.annotation.SuppressLint +import android.content.Context +import android.text.Spannable +import android.text.SpannableString +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.core.view.ViewCompat +import autodagger.AutoInjector +import com.nextcloud.talk.R +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.databinding.ItemSystemMessageBinding +import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.utils.DateUtils +import com.nextcloud.talk.utils.DisplayUtils +import com.nextcloud.talk.utils.preferences.AppPreferences +import com.stfalcon.chatkit.messages.MessageHolders +import javax.inject.Inject + +@AutoInjector(NextcloudTalkApplication::class) +class SystemMessageViewHolder(itemView: View) : MessageHolders.IncomingTextMessageViewHolder(itemView) { + + private val binding: ItemSystemMessageBinding = ItemSystemMessageBinding.bind(itemView) + + @JvmField + @Inject + var appPreferences: AppPreferences? = null + + @JvmField + @Inject + var context: Context? = null + + @JvmField + @Inject + var dateUtils: DateUtils? = null + protected var background: ViewGroup + + lateinit var systemMessageInterface: SystemMessageInterface + + init { + sharedApplication!!.componentApplication.inject(this) + background = itemView.findViewById(R.id.container) + } + + @SuppressLint("SetTextI18n") + override fun onBind(message: ChatMessage) { + super.onBind(message) + val resources = itemView.resources + val pressedColor: Int = resources.getColor(R.color.bg_message_list_incoming_bubble) + val mentionColor: Int = resources.getColor(R.color.textColorMaxContrast) + val bubbleDrawable = DisplayUtils.getMessageSelector( + resources.getColor(R.color.transparent), + resources.getColor(R.color.transparent), + pressedColor, + R.drawable.shape_grouped_incoming_message + ) + ViewCompat.setBackground(background, bubbleDrawable) + var messageString: Spannable = SpannableString(message.text) + if (message.messageParameters != null && message.messageParameters!!.size > 0) { + for (key in message.messageParameters!!.keys) { + val individualMap: Map? = message.messageParameters!![key] + if (individualMap != null && individualMap.containsKey("name")) { + var searchText: String? = if ("user" == individualMap["type"] || + "guest" == individualMap["type"] || + "call" == individualMap["type"] + ) { + "@" + individualMap["name"] + } else { + individualMap["name"] + } + messageString = DisplayUtils.searchAndColor(messageString, searchText, mentionColor) + } + } + } + + binding.systemMessageLayout.visibility = View.VISIBLE + binding.similarMessagesHint.visibility = View.GONE + if (message.expandableParent) { + binding.expandCollapseIcon.visibility = View.VISIBLE + + if (!message.isExpanded) { + val similarMessages = String.format( + sharedApplication!!.resources.getString(R.string.see_similar_system_messages), + message.expandableChildrenAmount + ) + + binding.messageText.text = messageString + binding.similarMessagesHint.visibility = View.VISIBLE + binding.similarMessagesHint.text = similarMessages + + binding.expandCollapseIcon.setImageDrawable( + ContextCompat.getDrawable(context!!, R.drawable.baseline_unfold_more_24) + ) + binding.systemMessageLayout.setOnClickListener { systemMessageInterface.expandSystemMessage(message) } + binding.messageText.setOnClickListener { systemMessageInterface.expandSystemMessage(message) } + } else { + binding.messageText.text = messageString + binding.similarMessagesHint.visibility = View.GONE + binding.similarMessagesHint.text = "" + + binding.expandCollapseIcon.setImageDrawable( + ContextCompat.getDrawable(context!!, R.drawable.baseline_unfold_less_24) + ) + binding.systemMessageLayout.setOnClickListener { systemMessageInterface.collapseSystemMessages() } + binding.messageText.setOnClickListener { systemMessageInterface.collapseSystemMessages() } + } + } else if (message.hiddenByCollapse) { + binding.systemMessageLayout.visibility = View.GONE + } else { + binding.expandCollapseIcon.visibility = View.GONE + binding.messageText.text = messageString + binding.expandCollapseIcon.setImageDrawable(null) + binding.systemMessageLayout.setOnClickListener(null) + } + + if (!message.expandableParent && message.lastItemOfExpandableGroup != 0) { + binding.systemMessageLayout.setOnClickListener { systemMessageInterface.collapseSystemMessages() } + binding.messageText.setOnClickListener { systemMessageInterface.collapseSystemMessages() } + } + + binding.messageTime.text = dateUtils!!.getLocalTimeStringFromTimestamp(message.timestamp) + itemView.setTag(R.string.replyable_message_view_tag, message.replyable) + } + + fun assignSystemMessageInterface(systemMessageInterface: SystemMessageInterface) { + this.systemMessageInterface = systemMessageInterface + } +} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java index dcc9e2370..a245c6045 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java @@ -74,6 +74,9 @@ public class TalkMessagesListAdapter extends MessagesListAda } else if (holder instanceof PreviewMessageViewHolder) { ((PreviewMessageViewHolder) holder).assignPreviewMessageInterface(chatActivity); ((PreviewMessageViewHolder) holder).assignCommonMessageInterface(chatActivity); + + } else if (holder instanceof SystemMessageViewHolder) { + ((SystemMessageViewHolder) holder).assignSystemMessageInterface(chatActivity); } } } 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 2f57f2ea4..74d8bfb88 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 @@ -136,6 +137,7 @@ import com.nextcloud.talk.adapters.messages.OutcomingTextMessageViewHolder import com.nextcloud.talk.adapters.messages.OutcomingVoiceMessageViewHolder import com.nextcloud.talk.adapters.messages.PreviewMessageInterface import com.nextcloud.talk.adapters.messages.PreviewMessageViewHolder +import com.nextcloud.talk.adapters.messages.SystemMessageInterface import com.nextcloud.talk.adapters.messages.SystemMessageViewHolder import com.nextcloud.talk.adapters.messages.TalkMessagesListAdapter import com.nextcloud.talk.adapters.messages.UnreadNoticeMessageViewHolder @@ -183,6 +185,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 @@ -263,7 +266,8 @@ class ChatActivity : ContentChecker, VoiceMessageInterface, CommonMessageInterface, - PreviewMessageInterface { + PreviewMessageInterface, + SystemMessageInterface { var active = false @@ -1908,6 +1912,45 @@ class ChatActivity : } } + @SuppressLint("NotifyDataSetChanged") + override fun collapseSystemMessages() { + adapter?.items?.forEach { + if (it.item is ChatMessage) { + val chatMessage = it.item as ChatMessage + if (isChildOfExpandableSystemMessage(chatMessage)) { + chatMessage.hiddenByCollapse = true + } + chatMessage.isExpanded = false + } + } + + adapter?.notifyDataSetChanged() + } + + private fun isChildOfExpandableSystemMessage(chatMessage: ChatMessage): Boolean { + return isSystemMessage(chatMessage) && + !chatMessage.expandableParent && + chatMessage.lastItemOfExpandableGroup != 0 + } + + @SuppressLint("NotifyDataSetChanged") + override fun expandSystemMessage(chatMessageToExpand: ChatMessage) { + adapter?.items?.forEach { + if (it.item is ChatMessage) { + val belongsToGroupToExpand = + (it.item as ChatMessage).lastItemOfExpandableGroup == chatMessageToExpand.lastItemOfExpandableGroup + + if (belongsToGroupToExpand) { + (it.item as ChatMessage).hiddenByCollapse = false + } + } + } + + chatMessageToExpand.isExpanded = true + + adapter?.notifyDataSetChanged() + } + @SuppressLint("LongLogTag") private fun downloadFileToCache(message: ChatMessage) { message.isDownloadingVoiceMessage = true @@ -3106,7 +3149,14 @@ class ChatActivity : Log.d(TAG, "pullChatMessages - HTTP_CODE_OK.") val chatOverall = response.body() as ChatOverall? - val chatMessageList = handleSystemMessages(chatOverall?.ocs!!.data!!) + + var chatMessageList = chatOverall?.ocs!!.data!! + + chatMessageList = handleSystemMessages(chatMessageList) + + determinePreviousMessageIds(chatMessageList) + + handleExpandableSystemMessages(chatMessageList) processHeaderChatLastGiven(response, lookIntoFuture) @@ -3121,6 +3171,8 @@ class ChatActivity : processMessagesFromTheFuture(chatMessageList) } else { processMessagesNotFromTheFuture(chatMessageList) + + collapseSystemMessages() } val newXChatLastCommonRead = response.headers()["X-Chat-Last-Common-Read"]?.let { @@ -3144,6 +3196,8 @@ class ChatActivity : isFirstMessagesProcessing = false binding.progressBar.visibility = View.GONE binding.messagesListView.visibility = View.VISIBLE + + collapseSystemMessages() } } @@ -3209,6 +3263,7 @@ class ChatActivity : } private fun processExpiredMessages() { + @SuppressLint("NotifyDataSetChanged") fun deleteExpiredMessages() { val messagesToDelete: ArrayList = ArrayList() val systemTime = System.currentTimeMillis() / ONE_SECOND_IN_MILLIS @@ -3269,8 +3324,6 @@ class ChatActivity : adapter?.addToStart(unreadChatMessage, false) } - determinePreviousMessageIds(chatMessageList) - addMessagesToAdapter(shouldAddNewMessagesNotice, chatMessageList) if (shouldAddNewMessagesNotice && adapter != null) { @@ -3278,6 +3331,36 @@ class ChatActivity : } } + private fun processMessagesNotFromTheFuture(chatMessageList: List) { + var countGroupedMessages = 0 + + for (i in chatMessageList.indices) { + if (chatMessageList.size > i + 1) { + if (isSameDayNonSystemMessages(chatMessageList[i], chatMessageList[i + 1]) && + chatMessageList[i + 1].actorId == chatMessageList[i].actorId && + countGroupedMessages < GROUPED_MESSAGES_THRESHOLD + ) { + chatMessageList[i].isGrouped = true + countGroupedMessages++ + } else { + countGroupedMessages = 0 + } + } + + val chatMessage = chatMessageList[i] + chatMessage.isOneToOneConversation = + currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL + chatMessage.isFormerOneToOneConversation = + (currentConversation?.type == ConversationType.FORMER_ONE_TO_ONE) + chatMessage.activeUser = conversationUser + } + + if (adapter != null) { + adapter?.addToEnd(chatMessageList, false) + } + scrollToRequestedMessageIfNeeded() + } + private fun scrollToFirstUnreadMessage() { adapter?.let { layoutManager?.scrollToPositionWithOffset( @@ -3307,10 +3390,8 @@ class ChatActivity : adapter?.let { chatMessage.isGrouped = ( - it.isPreviousSameAuthor( - chatMessage.actorId, - -1 - ) && it.getSameAuthorLastMessagesCount(chatMessage.actorId) % + it.isPreviousSameAuthor(chatMessage.actorId, -1) && + it.getSameAuthorLastMessagesCount(chatMessage.actorId) % GROUPED_MESSAGES_SAME_AUTHOR_THRESHOLD > 0 ) chatMessage.isOneToOneConversation = @@ -3339,37 +3420,6 @@ class ChatActivity : } } - private fun processMessagesNotFromTheFuture(chatMessageList: List) { - var countGroupedMessages = 0 - determinePreviousMessageIds(chatMessageList) - - for (i in chatMessageList.indices) { - if (chatMessageList.size > i + 1) { - if (isSameDayNonSystemMessages(chatMessageList[i], chatMessageList[i + 1]) && - chatMessageList[i + 1].actorId == chatMessageList[i].actorId && - countGroupedMessages < GROUPED_MESSAGES_THRESHOLD - ) { - chatMessageList[i].isGrouped = true - countGroupedMessages++ - } else { - countGroupedMessages = 0 - } - } - - val chatMessage = chatMessageList[i] - chatMessage.isOneToOneConversation = - currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL - chatMessage.isFormerOneToOneConversation = - (currentConversation?.type == ConversationType.FORMER_ONE_TO_ONE) - chatMessage.activeUser = conversationUser - } - - if (adapter != null) { - adapter?.addToEnd(chatMessageList, false) - } - scrollToRequestedMessageIfNeeded() - } - private fun determinePreviousMessageIds(chatMessageList: List) { var previousMessageId = NO_PREVIOUS_MESSAGE_ID for (i in chatMessageList.indices.reversed()) { @@ -3595,6 +3645,30 @@ class ChatActivity : return chatMessageMap.values.toList() } + private fun handleExpandableSystemMessages(chatMessageList: List): List { + val chatMessageMap = chatMessageList.map { it.id to it }.toMap().toMutableMap() + val chatMessageIterator = chatMessageMap.iterator() + while (chatMessageIterator.hasNext()) { + val currentMessage = chatMessageIterator.next() + + val previousMessage = chatMessageMap[currentMessage.value.previousMessageId.toString()] + if (isSystemMessage(currentMessage.value) && + previousMessage?.systemMessageType == currentMessage.value.systemMessageType + ) { + previousMessage?.expandableParent = true + currentMessage.value.expandableParent = false + + if (currentMessage.value.lastItemOfExpandableGroup == 0) { + currentMessage.value.lastItemOfExpandableGroup = currentMessage.value.jsonMessageId + } + + previousMessage?.lastItemOfExpandableGroup = currentMessage.value.lastItemOfExpandableGroup + previousMessage?.expandableChildrenAmount = currentMessage.value.expandableChildrenAmount + 1 + } + } + return chatMessageMap.values.toList() + } + private fun isInfoMessageAboutDeletion(currentMessage: MutableMap.MutableEntry): Boolean { return currentMessage.value.parentMessage != null && currentMessage.value.systemMessageType == ChatMessage .SystemMessageType.MESSAGE_DELETED @@ -3883,6 +3957,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/chat/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt index e38cffda1..0db7ea97b 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt @@ -135,7 +135,17 @@ data class ChatMessage( var voiceMessageSeekbarProgress: Int = 0, - var voiceMessageFloatArray: FloatArray? = null + var voiceMessageFloatArray: FloatArray? = null, + + var expandableParent: Boolean = false, + + var isExpanded: Boolean = false, + + var lastItemOfExpandableGroup: Int = 0, + + var expandableChildrenAmount: Int = 0, + + var hiddenByCollapse: Boolean = false ) : Parcelable, MessageContentType, MessageContentType.Image { 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 457195938..7d603af97 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() @@ -265,6 +266,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/drawable/baseline_unfold_less_24.xml b/app/src/main/res/drawable/baseline_unfold_less_24.xml new file mode 100644 index 000000000..3956a5c30 --- /dev/null +++ b/app/src/main/res/drawable/baseline_unfold_less_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/baseline_unfold_more_24.xml b/app/src/main/res/drawable/baseline_unfold_more_24.xml new file mode 100644 index 000000000..3b01290af --- /dev/null +++ b/app/src/main/res/drawable/baseline_unfold_more_24.xml @@ -0,0 +1,9 @@ + + + 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 eaabb88bd..2bb7db056 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 @@ + + + + + + + + + android:layout_height="wrap_content"> - + - + + + android:orientation="vertical" + android:padding="@dimen/standard_half_padding" + app:alignContent="stretch" + app:alignItems="stretch" + app:flexWrap="wrap" + app:justifyContent="flex_end"> + + + + + + + + + + - - diff --git a/app/src/main/res/values-es-rEC/strings.xml b/app/src/main/res/values-es-rEC/strings.xml index d856c99ea..fe507c075 100644 --- a/app/src/main/res/values-es-rEC/strings.xml +++ b/app/src/main/res/values-es-rEC/strings.xml @@ -395,6 +395,7 @@ La liga de la conversación no es válida Contraseña equivocada + Semana siguiente No hay integración de número de teléfono debido a permisos faltantes 1 hora En línea @@ -448,6 +449,7 @@ No tienes permiso para compartir contenido en este chat Enviar a... Enviar sin notificación + Establecer Establecer avatar desde la cámara Establecer estado Establecer mensaje de estado diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index bb437ab31..f8146d86b 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -35,6 +35,7 @@ %1$s(%2$d) 4 ordu Ikusezina + Beranduago gaur Kargatu emaitza gehiago Blokeo sinboloa Jaitsi eskua @@ -403,6 +404,7 @@ Elkarrizketa esteka ez da baliozkoa Pasahitz okerra Bai + Hurrengo astea Ezin izan da telefono zenbakia integratu, baimen falta dela eta Ordu 1 Linean @@ -456,6 +458,7 @@ Ez duzu edukirik partekatzeko baimentik txat honetan Bidali honi … Bidali jakinarazpenik gabe + Ezarri Ezarri avatarra kameraren bidez Ezarri egoera Ezarri egoera-mezua diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index de7f21ead..5e2669a5b 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -403,6 +403,7 @@ پیوند مکالمه معتبر نیست کلمه عبور اشتباه بله + هفتهٔ بعد No phone number integration due to missing permissions ۱ ساعت آنلاین @@ -456,6 +457,7 @@ You are not allowed to share content to this chat Send to … Send without notification + تنظیم Set avatar from camera تنظیم وضعیت تنظیم پیام وضعیت diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index cd69b4963..854b8dd31 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -27,6 +27,7 @@ %1$s (%2$d) 4 tuntia Näkymätön + Myöhemmin tänään Lataa lisää tuloksia Laske käsi Uusin ensin @@ -310,6 +311,7 @@ Keskustelulinkki ei ole kelvollinen Väärä salasana Kyllä + Seuraava viikko 1 tunti Paikalla Online-tila @@ -344,6 +346,7 @@ Sinulla ei ole oikeutta jakaa sisältöä tähän keskusteluun Lähetä… Lähetä ilman ilmoitusta + Aseta Aseta tilatieto Aseta tilaviesti Jaa diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 63ded0bab..40c021c3d 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -10,12 +10,14 @@ Avatar Absent(e) Options d\'appel avancées + L\'appel est en cours depuis une heure. Appeler sans notification Caméra autorisée. Merci de choisir à nouveau la caméra . Sélectionner l\'avatar depuis le cloud Effacer le message d\'état Effacer le message d\'état après Fermer + Verrouiller l\'enregistrement pour enregistrer en continu le message vocal Discussions Zone de danger Supprimer l\'avatar @@ -63,6 +65,7 @@ Autoriser les invités Épingler : %1$s Déverrouiller %1$s + Pour activer les écouteurs bluetooth, merci de donner la permission \"Appareils à proximité\". Options d\'appel avancées Répondre par appel vidéo Répondre par appel vocal uniquement @@ -86,6 +89,7 @@ %s appel %s appel vidéo %s appel audio + Pour activer la communication vidéo, merci de donner l\'autorisation \"Appareil photo\". Pour permettre les communications vidéo, veuillez autoriser l’utilisation de la caméra dans les paramètres du système. Annuler Échec de la récupération des capacités, abandon @@ -207,6 +211,7 @@ Annuler la réponse Message lu Message envoyé + Pour autoriser la communication audio, merci de donner l\'autorisation \"Microphone\". Pour permettre les communications audio, veuillez autoriser l’utilisation du microphone dans les paramètres du système. Vous avez manqué un appel de %s Modérateur @@ -240,7 +245,11 @@ Participants Ajouter des participants Mot de passe + Définir les autorisations + Certaines autorisations n\'ont pas été accordées. + Merci de donner les autorisations Ouvrir les paramètres + Merci de donner les autorisations dans Paramètres > Autorisations Compte introuvable Chat via %s Désactiver le micro @@ -394,6 +403,7 @@ Le lien de conversation n\'est pas valide. Mot de passe incorrect Oui + Semaine suivante Pas d\'intégration avec le carnet d\'adresses à cause d\'autorisations manquantes 1 heure En ligne @@ -447,6 +457,7 @@ Vous n\'êtes pas autorisé à partager du contenu dans cette conversation Envoyer a… Envoyer sans notification + Affecter Définir l\'avatar depuis la caméra Définir le statut Définir le message de statut diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 9b3109527..c5fa63243 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -19,6 +19,7 @@ Pechar Bloquear a gravación para gravar continuamente a mensaxe de voz Conversas + Personalizado Zona de perigo Eliminar avatar Non molestar @@ -35,6 +36,7 @@ %1$s (%2$d) 4 horas Invisíbel + Hoxe máis tarde Cargando máis resultados Símbolo de bloqueo Baixar a man @@ -403,6 +405,7 @@ A ligazón da conversa non é válida Contrasinal incorrecto Si + Semana seguinte Non hai integración do número de teléfono por mor da falta de permisos 1 hora En liña @@ -456,6 +459,7 @@ Non ten permiso para compartir contido nesta parola Enviar a… Enviar sen notificación + Estabelecer Estabelecer o avatar dende a cámara Estabelecer o estado Estabelecer a mensaxe de estado @@ -484,6 +488,7 @@ 30 minutos Esta semana Esta é unha mensaxe de proba + Este fin de semana Anexos Hoxe Traducir diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index d758e3ad5..b322abdda 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -11,6 +11,7 @@ Izbriši poruku statusa nakon Zatvori Razgovori + Prilagođeno Izbriši avatar Ne ometaj Ne briši @@ -328,6 +329,7 @@ Poveznica za razgovor nije valjana Pogrešna zaporka Da + Sljedeći tjedan Integracija broja telefona nije moguća jer nedostaje dopuštenje 1 sat Na mreži @@ -358,6 +360,7 @@ Odabrano Pošalji na Pošalji na… + Postavi Postavi status Postavi poruku statusa Dijeli diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml index ca1cb87f0..3baebcb7b 100644 --- a/app/src/main/res/values-hu-rHU/strings.xml +++ b/app/src/main/res/values-hu-rHU/strings.xml @@ -17,6 +17,7 @@ Állapotüzenet törlése ennyi idő után: Bezárás Beszélgetések + Egyéni Profilkép törlése Ne zavarjanak Ne törölje @@ -388,6 +389,7 @@ A beszélgetési hivatkozás nem érvényes Hibás jelszó Igen + Következő hét Nincs telefonszám integráció hiányzó engedélyek miatt 1 óra Elérhető @@ -441,6 +443,7 @@ Nincs jogosultsága, hogy tartalmat osszon meg ebben a csevegésben Küldés… Küldés értesítés nélkül + Beállítás Profilkép beállítása a kamerával Állapot beállítása Állapotüzenet beállítása diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index 3d314e2fe..26a9f2a23 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -10,6 +10,7 @@ Hreinsa stöðuskilaboð eftir Loka Samtöl + Sérsniðið Ónáðið ekki Ekki hreinsa Breyta @@ -233,6 +234,7 @@ Samskiptatengill er ekki gildur Rangt lykilorð + Næsta viku 1 klukkustund Á netinu Staða á netinu @@ -248,6 +250,7 @@ Útgefið sekúndum síðan Valið + Setja Setja stöðu Setja stöðuskilaboð Deila diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 810ab4183..56ecdca61 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -9,6 +9,7 @@ Auricolare cablato Avatar Assente + Calendario Opzioni avanzate per le chiamate Chiama senza notifica Autorizzazione fotocamera concessa. Scegli di nuovo la fotocamera. @@ -17,6 +18,7 @@ Cancella il messaggio di stato dopo Chiudi Conversazioni + Personalizzato Elimina avatar Non disturbare Non cancellare @@ -349,6 +351,7 @@ Il collegamento della conversazione non è valido Password errata + Settimana successiva Nessuna integrazione del numero di telefono a causa di autorizzazioni mancanti 1 ora In linea @@ -381,6 +384,7 @@ Selezionato Invia a Invia a… + Imposta Imposta stato Imposta messaggio di stato Condividi diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 2960e9928..43018c31d 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -9,6 +9,7 @@ מחיקת הודעת מצב לאחר סגירה דיונים + מותאם אישית לא להפריע לא לפנות עריכה @@ -246,6 +247,7 @@ הקישור לדיון אינו תקף ססמה שגויה כן + השבוע הבא שעה מקוון מצב מקוון @@ -260,6 +262,7 @@ לסנכרן מול שרתים מהימנים וספר הכתובות הגלובלי והציבורי לפני מספר שניות Selected + הגדרה הגדרת מצב הגדרת הודעת מצב שתף diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 1297beec2..04f03e2d0 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -9,6 +9,7 @@ 有線ヘッドセット アバター 離席中 + カレンダー 通話が1 時間経過 通知なしで通話 クラウドからアバターを選択 @@ -16,6 +17,7 @@ ステータスメッセージの有効期限 閉じる 会話 + カスタム アバターを削除 取り込み中 消去しない @@ -345,6 +347,7 @@ 会話リンクが無効です パスワードが間違っています はい + 来週 利用権限がないため、電話番号統合ができません。 1時間 オンライン @@ -382,6 +385,7 @@ このチャットにコンテンツを許可する権限がありません 送信… 通知なしで送信 + セット ステータスを設定 メッセージを設定 共有 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index d0cdd5102..a411c8600 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -9,6 +9,7 @@ 유선 헤드셋 아바타 자리비움 + 일정 알림 없이 전화하기 카메라 권한이 부여되었습니다 . 카메라를 다시 선택해주세요. 클라우드에서 아바타 선택 @@ -16,6 +17,7 @@ 상태 메시지 지우기 예약 닫기 대화 + 사용자 정의 아바타 삭제 방해 없음 지우지 않음 @@ -367,6 +369,7 @@ 무효한 대화 링크 잘못된 암호 + 다음주 권한 없음으로 인하여 전화 번호를 통합하지 않습니다. 1시간 접속 중 diff --git a/app/src/main/res/values-lt-rLT/strings.xml b/app/src/main/res/values-lt-rLT/strings.xml index 2728b3bfd..7921fe3ab 100644 --- a/app/src/main/res/values-lt-rLT/strings.xml +++ b/app/src/main/res/values-lt-rLT/strings.xml @@ -5,10 +5,12 @@ Telefonas Avataras Atsitraukęs + Kalendorius Išvalyti būsenos žinutę Išvalyti būsenos žinutę po Užverti Pokalbiai + Tinkinti Netrukdyti Neišvalyti Taisyti @@ -247,6 +249,7 @@ Pokalbio nuoroda negalioja Neteisingas slaptažodis Taip + Kita savaitė 1 valanda Prisijungęs Prisijungimo būsena @@ -274,6 +277,7 @@ Slinkti į apačią prieš keletą sekundžių Pasirinkta + Nustatyti Nustatyti būseną Nustatyti būsenos žinutę Bendrinti diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 4a2f79fc6..13c516203 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -8,10 +8,12 @@ Høytaler Avatar Borte + Kalender Fjern statusmelding Fjern statusmelding etter Lukk Samtaler + Egendefinert Slett data Ikke forstyrr Ikke fjern @@ -23,6 +25,7 @@ %1$s (%2$d) 4 timer Usynlig + Senere i dag Hent flere resultat Senk hånden Nyeste først @@ -267,6 +270,7 @@ Samtale lenke er ikke gyldig Feil passord Ja + Neste uke 1 time Pålogget Online-status @@ -290,6 +294,7 @@ Valgt Send til Send til... + Sett Velg status Velg statusmelding Del @@ -309,6 +314,7 @@ Lommelykt av/på 30 minutter Denne uken + Denne helgen Vedlegg I dag Oversette diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 2463f155a..29113d1b2 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -9,6 +9,7 @@ Bedrade headset Avatar Afwezig + Agenda Geavanceerde oproepopties Oproepen zonder melding Toegang tot camera verleend. Kies aub. uw camera opnieuw. @@ -17,6 +18,7 @@ Statusbericht wissen na Sluit Gesprekken + Aangepast Verwijder avatar Niet storen Niet opruimen @@ -357,6 +359,7 @@ Kies er eentje van een provider. Gesprekslink is niet geldig Onjuist wachtwoord Ja + Volgende week Geen telefoonnummer-integratie wegens ontbrekende machtigingen 1 uur Online @@ -400,6 +403,7 @@ Kies er eentje van een provider. Versturen naar Versturen naar … Versturen zonder melding + Stel in Instellen status Statusbericht instellen Delen @@ -424,6 +428,7 @@ Kies er eentje van een provider. Deze week Bijlagen Vandaag + Morgen Apparaatinstellingen Kan taal niet detecteren Van diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index b95b5ac88..0201cb64f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -9,6 +9,7 @@ Słuchawki przewodowe Awatar Bezczynny + Kalendarz Zaawansowane opcje połączeń Połącz bez powiadomienia Udzielono dostępu do kamery. Proszę ponownie wybrać kamerę. @@ -18,6 +19,7 @@ Zamknij Zablokuj nagrywanie w celu ciągłego nagrywania wiadomości głosowej Rozmowy + Dowolnie Strefa niebezpieczeństwa Usuń awatar Nie przeszkadzać @@ -395,6 +397,7 @@ Link rozmowy jest nieprawidłowy Złe hasło Tak + Następny tydzień Brak integracji numeru telefonu z powodu braku uprawnień 1 godzina Online @@ -448,6 +451,7 @@ Nie możesz udostępniać treści na tym czacie Wyślij do… Wyślij bez powiadomienia + Ustaw Ustaw awatar z aparatu Ustaw status Ustaw komunikat statusu @@ -478,6 +482,7 @@ To jest wiadomość testowa Załączniki Dzisiaj + Jutro Tłumaczenie Tłumaczenie Kopiuj przetłumaczony tekst diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index a8520305e..acb665cdb 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -9,6 +9,7 @@ Fone de ouvido Avatar Ausente + Calendário Opções avançadas de chamada A chamada está em execução há uma hora. Ligue sem notificação @@ -19,6 +20,7 @@ Fechar Gravação de bloqueio para gravação contínua da mensagem de voz Conversas + Personalizar Zona de perigo Excluir avatar Não perturbe @@ -35,6 +37,7 @@ %1$s (%2$d) 4 horas Invisível + Hoje mais tarde Carregar mais resultados Símbolo de cadeado Baixar a mão @@ -403,6 +406,7 @@ O link de conversação não é válido Senha incorreta Sim + Próxima semana Sem integração de número de telefone devido à falta de permissões 1 hora Online @@ -456,6 +460,7 @@ Você não tem permissão para compartilhar conteúdo neste bate-papo Enviar para … Enviar sem notificação + Definir Definir avatar da câmera Definir status Definir mensagem de status @@ -484,8 +489,10 @@ 30 minutos Esta semana Esta é uma mensagem de teste + Este fim de semana Anexos Hoje + Amanhã Traduzir Tradução Copiar texto traduzido diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index bbbcbc1a7..26e172989 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -9,6 +9,7 @@ Проводная гарнитура Аватар Отошёл + Календарь Дополнительные настройки звонка Звонок длится уже час Звонок без уведомления @@ -18,6 +19,7 @@ Очищать статус после Закрыть Разговоры + Задать Удалить аватар Не беспокоить Не очищать @@ -33,6 +35,7 @@ %1$s (%2$d) 4 часа Невидимый + Позже сегодня Показать больше результатов Символ блокировки Опустить руку @@ -385,6 +388,7 @@ Ссылка на беседу более не действительна Неправильный пароль Да + Следующая неделя Отсутствуют разрешения на интеграцию номера телефона 1 час В сети @@ -438,6 +442,7 @@ Вам не разрешено делиться контентом в этом чате Отправить … Отправить без уведомления + Указать Установить аватар со снимка камерой Установить статус Установить статус @@ -465,8 +470,10 @@ Переключатель фонарика 30 минут Эта неделя + Эта неделя Вложения Сегодня + Завтра Помочь с переводом Перевод Копировать переведенный текст diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 97751d444..61f45c552 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -6,11 +6,13 @@ Altoparlante Avatar Ausente + Calendàriu Sèbera s\'avatar dae sa nue virtuale Lìmpia su messàgiu de istadu Lìmpia su messàgiu de istadu a pustis Serra Cunversatziones + Personaliza Cantzella s\'avatar No istorbes Modìfica @@ -314,6 +316,7 @@ Ligòngiu de tzarrada non vàlidu Crae isballiada Eja + Sa chida chi benit Peruna integratzione de nùmeru de telèfonu pro farta de permissos 1 ora In lìnia @@ -362,6 +365,7 @@ Custa chida Alligongiados Oe + Cras Borta Impostatziones de su dispositivu Dae diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml index dbf9531aa..9e626a0b6 100644 --- a/app/src/main/res/values-sk-rSK/strings.xml +++ b/app/src/main/res/values-sk-rSK/strings.xml @@ -9,6 +9,7 @@ Drátové sluchátka Avatar Preč + Kalendár Volať bez upozornenia Povolenie fotoaparátu bolo udelené. Vyberte fotoaparát znova. Vyberte si avatara z cloudu @@ -16,6 +17,7 @@ Vyčistiť správu o stave po Zatvoriť Konverzácie + Vlastný Zmazať avatara Nerušiť Nemazať @@ -233,6 +235,7 @@ Push notifikácie vypnuté Stlač a hovor PTT S vypnutým mikrofónom, kliknite &podržte pre použitie funkcie Stlač a hovor PTT + Pripomenúť neskôr Vypnúť vzdialené audio Vymazať kruh a členov Odstrániť z obľúbených @@ -369,6 +372,7 @@ Odkaz na rozhovor nie je platný! Nesprávne heslo Áno + Nasledujúci týždeň Registrácia telefónneho čísla nie je povolená pre nedostatok práv 1 hodina Pripojený @@ -413,6 +417,7 @@ Nemáte oprávnenie zdieľať obsah tohto rozhovoru Odoslať do … Poslať bez upozornenia + Nastaviť Nastaviť avatara z fotoaparátu Nastaviť stav Nastaviť správu o stave @@ -439,6 +444,7 @@ Tento týždeň Prílohy Dnes + Zajtra Preložiť Nastavenia zariadenia Od diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 43f2983b7..82e0a72dd 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -9,6 +9,7 @@ Ožičene slušalke Podoba Ne spremljam + Koledar Napredne možnosti klica Klic brez obvestila Dovoljenje za uporabo kamere je dodeljeno. Ponovno izberite kamero. @@ -17,6 +18,7 @@ Počisti sporočilo stanja po Zapri Pogovori + Po meri Izbriši podobo Ne moti Ne počisti @@ -245,6 +247,7 @@ Potisno obveščanje je onemogočeno Možnost »push-to-talk« Pri onemogočenem mikrofonu lahko za začetek govora kliknete in zadržite gumb + Opomni me kasneje Oddaljen zvok je onemogočen Odstrani krog in člane Odstrani iz priljubljenih @@ -381,6 +384,7 @@ Povezava do pogovora ni veljavna Napačno geslo Da + Naslednji teden Povezava s telefonskim imenikom ni na voljo zaradi neustreznih dovoljenj. 1 uri Trenutno na spletu @@ -432,6 +436,7 @@ Ni ustreznih dovoljenj za objavljanje vsebine v ta klepet Pošlji … Pošlji brez obvestila + Nastavi Nastavi sličico s kamero Nastavi stanje Nastavi sporočilo stanja @@ -461,6 +466,7 @@ Ta teden Priloge Danes + Jutri Prevodi Prevod Kopiraj prevedeno besedilo diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 5d36c90f8..42ac6e94c 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -9,6 +9,7 @@ Жичане слушалице са микрофоном Аватар Одсутан + Календар Напредне опције позива Позив траје један сат. Позив без обавештења @@ -19,6 +20,7 @@ Затвори Закључајте снимање да би се гласовна порука непрестано снимала. Разговори + Прилагођено Зона опасности Обриши аватар Не узнемиравај @@ -35,6 +37,7 @@ %1$s (%2$d) 4 сата Невидљива + Касније данас Учитај још резултата Симбол катанца Спуштена рука @@ -264,6 +267,7 @@ Брза обавештења су искључена Притисни за разговор Са онемогућеним микрофоном, кликните & држите да користите притисак за разговор + Подсети ме касније Удаљени аудио је искључен Уклони круг и чланове Уклони из омиљених @@ -403,6 +407,7 @@ Веза разговора није исправна Погрешна лозинка Да + Наредне недеље Нема интеграције броја телефона јер недостају дозволе 1 сат На мрежи @@ -456,6 +461,7 @@ Није вам дозвољено да делите садржај у овај чет Пошаљи у ... Пошаљи без обавештења + Постави Постави аватар са камере Постави статус Постави статусну поруку @@ -484,8 +490,10 @@ 30 минута Ове недеље Ово је тест порука + Овог викенда Прилози Данас + Сутра Превођење Превод Копирај преведени текст diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 8b07c6b75..996a4362e 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -8,11 +8,13 @@ Sladdansluten hörlur Avatar Iväg + Kalender Välj avatar från moln Rensa statusmeddelande Rensa statusmeddelande efter Stäng Konversationer + Anpassad Ta bort avatar Stör ej Rensa inte @@ -27,6 +29,7 @@ %1$s (%2$d) 4 timmar Osynlig + Senare idag Visa fler resultat Låssymbol Nyast först @@ -177,6 +180,7 @@ Push-aviseringar inaktiverade Push-to-talk Med mikrofon inaktiverad klickar du på &vänta för att använda Push-to-talk + Påminn mig senare Ta bort från favoriter Ta bort deltagare Byt namn på konversation @@ -281,6 +285,7 @@ Konversationslänk är inte giltig Fel lösenord Ja + Nästa vecka 1 timme Online Online-status @@ -303,6 +308,7 @@ Ändra hemlighetsnivå för %1$s sekunder sedan Vald + Sätt Sätt status Sätt statusmeddelande Dela @@ -319,8 +325,10 @@ Starta lampa 30 minuter Denna vecka + Denna helgen Bilagor Idag + I morgon Översätt Upptäck språk Enhetsinställningar diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 9626ffbcb..2dec4230d 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -9,6 +9,7 @@ Kablolu kulaklık Avatar Uzakta + Takvim Gelişmiş çağrı seçenekleri Çağrı bir saattir sürüyor. Bildirim olmadan çağrı @@ -19,6 +20,7 @@ Kapat Sesli iletinin sürekli olarak kaydedilmesi için kaydı kilitleyin Görüşmeler + Özel Tehlikeli bölge Avatarı sil Rahatsız etmeyin @@ -35,6 +37,7 @@ %1$s (%2$d) 4 saat Görünmez + Bugün daha sonra Diğer sonuçları yükle Kilit simgesi Eli indir @@ -264,6 +267,7 @@ Anında bildirimler devre dışı Bas-konuş Mikrofon devre dışı iken, Bas-konuş üzerine tıklayıp basılı tutun + Sonra hatırlat Uzak ses kapalı Çevreyi ve üyelerini sil Sık kullanılanlardan kaldır @@ -403,6 +407,7 @@ Görüşme bağlantısı geçersiz Parola yanlış Evet + Sonraki hafta İzinler eksik olduğundan telefon numarası bütünleştirmesi yok 1 saat Çevrim içi @@ -456,6 +461,7 @@ Bu sohbette içerik paylaşma izniniz yok Şuraya gönder … Bildirim olmadan gönder + Ayarla Avatarı kamerayla ayarla Durumu ayarla Durum iletisini ayarla @@ -484,8 +490,10 @@ 30 dakika Bu hafta Bu bir deneme iletisidir + Bu hafta sonu Ek dosyalar Bugün + Yarın Çevir Çeviri Çevrilmiş metni kopyala diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index f7243bfc0..3fefcf56f 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -9,6 +9,7 @@ Дротова гарнітура Світлина Немає поряд + Календар Розширені опції дзвінка Дзвінок без сповіщення Дозвіл на використання камери надано. Будь ласка, виберіть камеру ще раз. @@ -17,6 +18,7 @@ Очистити повідомлення про стан після Закрити Розмови + Власне Вилучити світлину Не турбувати Не очищати @@ -244,6 +246,7 @@ Push сповіщення вимкнено Тисни-та-кажи Натисніть і утримуйте & для використання PTT при вимкненому мікрофоні + Нагадати пізніше Віддалене аудіо відключено Вилучити кола та учасників Вилучено з улюбленого @@ -376,6 +379,7 @@ Посилання на бесіду більше не дійсне Неправильний пароль Так + Наступний тиждень Відсутні дозволи на інтеграцію номера телефону 1 година Онлайн @@ -415,6 +419,7 @@ Опубліковано секунд тому Selected + Встановити Встановити статус Встановити повідомлення про стан Спільний доступ @@ -431,6 +436,7 @@ Цього тижня Вкладення Сьогодні + Завтра Перекласти Визначити мову Налаштування пристрою diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 9d127d19f..b42f6f1cc 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -5,11 +5,13 @@ Điện thoại Hình đại diện Tạm vắng + Lịch Chọn hình đại diện từ đám mây Xoá thông báo trạng thái Xoá thông báo trạng thái sau Đóng Đàm thoại + Tùy chỉnh Xóa hình đại diện Đừng làm phiền Không xoá @@ -214,6 +216,7 @@ Liên kết đàm thoại này không có thực Sai mật khẩu + Tuần sau 1 tiếng Trực tuyến Trạng thái trực tuyến @@ -234,6 +237,7 @@ Cuộn xuống dưới cùng vài giây trước Selected + Đặt Đặt trạng thái Đặt thông báo trạng thái Chia sẻ @@ -252,6 +256,7 @@ Tuần này Đính kèm Hôm nay + Ngày mai Dịch Cài đặt thiết bị Không thể phát hiện ngôn ngữ diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 9241bd5ba..220ce1cd4 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -9,6 +9,7 @@ 有线耳机 头像 离开 + 日历 高级呼叫选项 无通知呼叫 摄像头访问已授权。请重新选择摄像头。 @@ -17,6 +18,7 @@ 过多长时间清除状态消息 关闭 对话 + 自定义 危险区域 删除头像 不要打扰 @@ -255,6 +257,7 @@ 通知推送已禁用 按键讲话 禁用麦克风的情况下,点击&并按住使用“按键讲话”功能 + 以后提醒我 远程音频关闭 删除圈子和成员 取消收藏 @@ -394,6 +397,7 @@ 对话链接无效 错误的密码 是的 + 下周 由于缺少权限,没有电话集成 1小时 在线 @@ -447,6 +451,7 @@ 你不能在此聊天中分享内容 发送到 … 发送而不通知 + 设置 从摄像头设置头像 设定状态 设置状态消息 @@ -477,6 +482,7 @@ 这是一个测试消息 附件 今天 + 明天 翻译 翻译 复制已翻译的文字 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index da24f1f25..53e05103e 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -9,6 +9,7 @@ 有線耳機 虛擬化身大頭照 不在 + 日曆 進階通話選項 請注意,通話已經持續一個小時了。 通話而不通知 @@ -19,6 +20,7 @@ 關閉 鎖定錄音以連續錄製語音訊息 對話 + 自訂 危險地帶 刪除虛擬化身大頭照 請勿打擾 @@ -35,6 +37,7 @@ %1$s(%2$d) 4 小時 隱藏 + 今日稍後 正在載入更多結果 鎖符號 放下手 @@ -264,6 +267,7 @@ 取消推送通知 一鍵通 在停用米高風的情況下,單擊並按住即可使用一鍵通 + 稍後提醒我 遠端語音關閉 移除社交圈子及組員 取消我的最愛 @@ -403,6 +407,7 @@ 對話連結無效 密碼錯誤 + 下星期 由於缺少權限而無法集成電話號碼 1 小時 在線 @@ -456,6 +461,7 @@ 您不能在此聊天中分享內容 傳送到 … 傳送而不通知 + 設置 從相機設置虛擬化身大頭照 設定狀態 設定狀態訊息 @@ -484,8 +490,10 @@ 30 分鐘 本星期 此乃測試訊息 + 本週末 附件 今日 + 明日 翻譯 翻譯 複製已翻譯的文字 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index b7029a157..7f70d5367 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -9,6 +9,7 @@ 有線式頭戴耳機 頭像 外出 + 日曆 進階通話選項 通話已經持續一個小時了。 通話而不通知 @@ -19,6 +20,7 @@ 關閉 鎖定錄音以連續錄製語音訊息 對話 + 自訂 危險地帶 刪除頭像 請勿打擾 @@ -35,6 +37,7 @@ %1$s (%2$d) 4小時 隱藏 + 今天稍後 載入更多結果 上鎖符號 放手 @@ -264,6 +267,7 @@ 取消推送通知 按住以說話 在停用麥克風的情況下,點擊並按住即可使用按住以說話 + 稍後提醒我 遠端音訊關閉 移除小圈圈與成員 取消我的最愛 @@ -403,6 +407,7 @@ 對話連結無效 密碼錯誤 + 下週 因為缺少權限而無法整合電話號碼 1小時 線上 @@ -456,6 +461,7 @@ 您不被允許在此聊天中分享內容 傳送至…… 傳送而不通知 + 設定 從相機設定大頭照 設定狀態 設定狀態訊息 @@ -484,8 +490,10 @@ 30分鐘 這個禮拜 這是測試訊息 + 本週末 附件 今天 + 明天 翻譯 翻譯 複製已翻譯的文字 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b3514534b..dca3644d0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -376,6 +376,7 @@ How to translate with transifex: Add attachment Recent Backspace + See %1$s similar messages Guest access @@ -697,5 +698,13 @@ How to translate with transifex: 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