From 98e2117bdfd95b46256da13a753071f9c37423dc Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Mon, 14 Dec 2020 14:42:35 +0100 Subject: [PATCH] Missing imports after rebase Signed-off-by: tobiasKaminsky --- .idea/compiler.xml | 2 +- .idea/modules.xml | 2 +- .../MagicOutcomingTextMessageViewHolder.kt | 19 +++++++- .../messages/TalkMessagesListAdapter.java | 39 ++++++++++++++++ .../java/com/nextcloud/talk/api/NcApi.java | 4 ++ .../talk/controllers/ChatController.kt | 35 ++++++++++++--- .../talk/controllers/SettingsController.java | 45 +++++++++++++++++++ .../nextcloud/talk/models/database/User.java | 21 +++++++++ .../talk/models/json/chat/ChatMessage.java | 14 ++++-- .../talk/models/json/chat/ReadStatus.java | 25 +++++++++++ .../com/nextcloud/talk/utils/ApiUtils.java | 4 ++ .../utils/preferences/AppPreferences.java | 11 +++++ app/src/main/res/drawable/ic_check.xml | 30 +++++++++++++ app/src/main/res/drawable/ic_check_all.xml | 30 +++++++++++++ .../main/res/layout/controller_settings.xml | 8 ++++ .../item_custom_outcoming_text_message.xml | 8 ++++ app/src/main/res/values/strings.xml | 3 ++ 17 files changed, 287 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/chat/ReadStatus.java create mode 100644 app/src/main/res/drawable/ic_check.xml create mode 100644 app/src/main/res/drawable/ic_check_all.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 74d8d3349..0cf1b28a3 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -9,4 +9,4 @@ - \ No newline at end of file + diff --git a/.idea/modules.xml b/.idea/modules.xml index 61d732101..374127381 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -6,4 +6,4 @@ - \ No newline at end of file + diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt index 08f77b2c1..774c5edf8 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt @@ -21,6 +21,7 @@ package com.nextcloud.talk.adapters.messages import android.content.Context import android.content.Intent +import android.graphics.PorterDuff import android.net.Uri import android.text.Spannable import android.text.SpannableString @@ -36,12 +37,12 @@ import butterknife.BindView import butterknife.ButterKnife import coil.api.load import coil.transform.CircleCropTransformation -import com.facebook.drawee.view.SimpleDraweeView import com.google.android.flexbox.FlexboxLayout import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.models.json.chat.ReadStatus import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DisplayUtils.getMessageSelector import com.nextcloud.talk.utils.DisplayUtils.searchAndReplaceWithMentionSpan @@ -88,6 +89,10 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage @BindView(R.id.quoteColoredView) var quoteColoredView: View? = null + @JvmField + @BindView(R.id.checkMark) + var checkMark: ImageView? = null + @JvmField @Inject var context: Context? = null @@ -181,6 +186,18 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage quotedChatMessageView?.visibility = View.GONE } + val readStatusDrawableInt = when (message.readStatus) { + ReadStatus.READ -> R.drawable.ic_check_all + ReadStatus.SENT -> R.drawable.ic_check + else -> null + } + + readStatusDrawableInt?.let { + context?.resources?.getDrawable(it, null)?.let { + it.setColorFilter(context?.resources!!.getColor(R.color.warm_grey_four), PorterDuff.Mode.SRC_ATOP) + checkMark?.setImageDrawable(it) + } + } } init { 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 new file mode 100644 index 000000000..d8f478fbf --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java @@ -0,0 +1,39 @@ +/* + * Nextcloud Talk application + * + * @author Tobias Kaminsky + * Copyright (C) 2020 Tobias Kaminsky + * + * 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 com.stfalcon.chatkit.commons.ImageLoader; +import com.stfalcon.chatkit.commons.models.IMessage; +import com.stfalcon.chatkit.messages.MessageHolders; +import com.stfalcon.chatkit.messages.MessagesListAdapter; + +import java.util.List; + +public class TalkMessagesListAdapter extends MessagesListAdapter { + + public TalkMessagesListAdapter(String senderId, MessageHolders holders, ImageLoader imageLoader) { + super(senderId, holders, imageLoader); + } + + public List getItems() { + return items; + } +} 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 f9750f4ee..b11da3786 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -342,6 +342,10 @@ public interface NcApi { @Field("timer") Long timer); @POST + Observable setReadStatusPrivacy(@Header("Authorization") String authorization, + @Url String url, + @Body RequestBody body); + @POST Observable searchContactsByPhoneNumber(@Header("Authorization") String authorization, @Url String url, @Body RequestBody search); diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index fd3206aab..4c95d9546 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -73,6 +73,7 @@ import com.nextcloud.talk.events.WebSocketCommunicationEvent import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatOverall +import com.nextcloud.talk.models.json.chat.ReadStatus import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.conversations.RoomsOverall @@ -174,7 +175,7 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter var historyRead = false var globalLastKnownFutureMessageId = -1 var globalLastKnownPastMessageId = -1 - var adapter: MessagesListAdapter? = null + var adapter: TalkMessagesListAdapter? = null var mentionAutocomplete: Autocomplete<*>? = null var layoutManager: LinearLayoutManager? = null var lookingIntoFuture = false @@ -361,7 +362,7 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter MagicUnreadNoticeMessageViewHolder::class.java, R.layout.item_date_header, MagicUnreadNoticeMessageViewHolder::class.java, R.layout.item_date_header, this) - adapter = MessagesListAdapter(conversationUser?.userId, messageHolders, ImageLoader { imageView, url, payload -> + adapter = TalkMessagesListAdapter(conversationUser?.userId, messageHolders, ImageLoader { imageView, url, payload -> val draweeController = Fresco.newDraweeControllerBuilder() .setImageRequest(DisplayUtils.getImageRequestForUrl(url, conversationUser)) .setControllerListener(DisplayUtils.getImageControllerListener(imageView)) @@ -932,7 +933,7 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter } } - fun pullChatMessages(lookIntoFuture: Int, setReadMarker: Int = 1) { + fun pullChatMessages(lookIntoFuture: Int, setReadMarker: Int = 1, xChatLastCommonRead: Int? = -1) { if (!inConversation) { return } @@ -974,6 +975,9 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter } fieldMap["lastKnownMessageId"] = lastKnown + xChatLastCommonRead?.let { + fieldMap["lastCommonReadId"] = it + } if (!wasDetached) { if (lookIntoFuture > 0) { @@ -989,10 +993,9 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter override fun onNext(response: Response<*>) { if (response.code() == 304) { - pullChatMessages(1, setReadMarker) + pullChatMessages(1, setReadMarker, xChatLastCommonRead) } else if (response.code() == 412) { futurePreconditionFailed = true - } else { processMessages(response, true, finalTimeout) } @@ -1038,6 +1041,9 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter private fun processMessages(response: Response<*>, isFromTheFuture: Boolean, timeout: Int) { val xChatLastGivenHeader: String? = response.headers().get("X-Chat-Last-Given") + val xChatLastCommonRead = response.headers().get("X-Chat-Last-Common-Read")?.let { + Integer.parseInt(it) + } if (response.headers().size() > 0 && !TextUtils.isEmpty(xChatLastGivenHeader)) { val header = Integer.parseInt(xChatLastGivenHeader!!) @@ -1089,7 +1095,6 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter chatMessage.isOneToOneConversation = currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL chatMessage.isLinkPreviewAllowed = isLinkPreviewAllowed chatMessage.activeUser = conversationUser - } if (adapter != null) { @@ -1155,8 +1160,24 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter } + // update read status of all messages + for (message in adapter!!.items) { + xChatLastCommonRead?.let { + if (message.item is ChatMessage) { + val chatMessage = message.item as ChatMessage + + if (chatMessage.jsonMessageId <= it) { + chatMessage.readStatus = ReadStatus.READ + } else { + chatMessage.readStatus = ReadStatus.SENT + } + } + } + } + adapter?.notifyDataSetChanged() + if (inConversation) { - pullChatMessages(1) + pullChatMessages(1, 1, xChatLastCommonRead) } } else if (response.code() == 304 && !isFromTheFuture) { if (isFirstMessagesProcessing) { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java index d3ec45b52..ca85b9307 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java @@ -57,6 +57,7 @@ import com.nextcloud.talk.jobs.AccountRemovalWorker; import com.nextcloud.talk.jobs.ContactAddressBookWorker; import com.nextcloud.talk.models.RingtoneSettings; import com.nextcloud.talk.models.database.UserEntity; +import com.nextcloud.talk.models.json.generic.GenericOverall; import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.DisplayUtils; import com.nextcloud.talk.utils.DoNotDisturbUtils; @@ -98,9 +99,12 @@ import androidx.work.WorkManager; import autodagger.AutoInjector; import butterknife.BindView; import butterknife.OnClick; +import io.reactivex.Observer; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import okhttp3.MediaType; +import okhttp3.RequestBody; @AutoInjector(NextcloudTalkApplication.class) public class SettingsController extends BaseController { @@ -157,6 +161,8 @@ public class SettingsController extends BaseController { MaterialChoicePreference screenLockTimeoutChoicePreference; @BindView(R.id.settings_phone_book_integration) MaterialSwitchPreference phoneBookIntegretationPreference; + @BindView(R.id.settings_read_privacy) + MaterialSwitchPreference readPrivacyPreference; @BindView(R.id.message_text) TextView messageText; @@ -177,6 +183,7 @@ public class SettingsController extends BaseController { private OnPreferenceValueChangedListener screenLockChangeListener; private OnPreferenceValueChangedListener screenLockTimeoutChangeListener; private OnPreferenceValueChangedListener themeChangeListener; + private OnPreferenceValueChangedListener readPrivacyChangeListener; private OnPreferenceValueChangedListener phoneBookIntegrationChangeListener; private Disposable profileQueryDisposable; @@ -215,6 +222,7 @@ public class SettingsController extends BaseController { appPreferences.registerThemeChangeListener(themeChangeListener = new ThemeChangeListener()); appPreferences.registerPhoneBookIntegrationChangeListener( phoneBookIntegrationChangeListener = new PhoneBookIntegrationChangeListener(this)); + appPreferences.registerReadPrivacyChangeListener(readPrivacyChangeListener = new ReadPrivacyChangeListener()); List listWithIntFields = new ArrayList<>(); listWithIntFields.add("proxy_port"); @@ -449,6 +457,8 @@ public class SettingsController extends BaseController { ((Checkable) incognitoKeyboardSwitchPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.getIsKeyboardIncognito()); } + ((Checkable) readPrivacyPreference.findViewById(R.id.mp_checkable)).setChecked(!currentUser.isReadStatusPrivate()); + ((Checkable) linkPreviewsSwitchPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.getAreLinkPreviewsAllowed()); ((Checkable) phoneBookIntegretationPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.isPhoneBookIntegrationEnabled()); @@ -661,6 +671,7 @@ public class SettingsController extends BaseController { appPreferences.unregisterScreenLockListener(screenLockChangeListener); appPreferences.unregisterScreenLockTimeoutListener(screenLockTimeoutChangeListener); appPreferences.unregisterThemeChangeListener(themeChangeListener); + appPreferences.unregisterReadPrivacyChangeListener(readPrivacyChangeListener); appPreferences.unregisterPhoneBookIntegrationChangeListener(phoneBookIntegrationChangeListener); } super.onDestroy(); @@ -847,4 +858,38 @@ public class SettingsController extends BaseController { } } } + + private class ReadPrivacyChangeListener implements OnPreferenceValueChangedListener { + @Override + public void onChanged(Boolean newValue) { + String booleanValue = newValue ? "0" : "1"; + String json = "{\"key\": \"read_status_privacy\", \"value\" : " + booleanValue + "}"; + + ncApi.setReadStatusPrivacy( + ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken()), + ApiUtils.getUrlForUserSettings(currentUser.getBaseUrl()), + RequestBody.create(MediaType.parse("application/json"), json)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { + } + + @Override + public void onNext(@io.reactivex.annotations.NonNull GenericOverall genericOverall) { + } + + @Override + public void onError(@io.reactivex.annotations.NonNull Throwable e) { + appPreferences.setReadPrivacy(!newValue); + ((Checkable) readPrivacyPreference.findViewById(R.id.mp_checkable)).setChecked(!newValue); + } + + @Override + public void onComplete() { + } + }); + } + } } diff --git a/app/src/main/java/com/nextcloud/talk/models/database/User.java b/app/src/main/java/com/nextcloud/talk/models/database/User.java index f3bffe15e..593f1cb45 100644 --- a/app/src/main/java/com/nextcloud/talk/models/database/User.java +++ b/app/src/main/java/com/nextcloud/talk/models/database/User.java @@ -146,4 +146,25 @@ public interface User extends Parcelable, Persistable, Serializable { } return false; } + + default boolean isReadStatusPrivate() { + if (getCapabilities() != null) { + Capabilities capabilities; + try { + capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class); + if (capabilities != null && + capabilities.getSpreedCapability() != null && + capabilities.getSpreedCapability().getConfig() != null && + capabilities.getSpreedCapability().getConfig().containsKey("chat")) { + HashMap map = capabilities.getSpreedCapability().getConfig().get("chat"); + if (map != null && map.containsKey("read-privacy")) { + return Integer.parseInt(map.get("read-privacy")) == 1; + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return false; + } } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java index 9be6c4bd5..4f8eec046 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java @@ -20,7 +20,7 @@ package com.nextcloud.talk.models.json.chat; import android.text.TextUtils; -import androidx.annotation.Nullable; + import com.bluelinelabs.logansquare.annotation.JsonField; import com.bluelinelabs.logansquare.annotation.JsonIgnore; import com.bluelinelabs.logansquare.annotation.JsonObject; @@ -33,10 +33,17 @@ import com.nextcloud.talk.utils.TextMatchers; import com.stfalcon.chatkit.commons.models.IMessage; import com.stfalcon.chatkit.commons.models.IUser; import com.stfalcon.chatkit.commons.models.MessageContentType; -import lombok.Data; + import org.parceler.Parcel; -import java.util.*; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import androidx.annotation.Nullable; +import lombok.Data; @Parcel @Data @@ -77,6 +84,7 @@ public class ChatMessage implements IMessage, MessageContentType, MessageContent public boolean replyable; @JsonField(name = "parent") public ChatMessage parentMessage; + public Enum readStatus = ReadStatus.NONE; @JsonIgnore List messageTypesToIgnore = Arrays.asList(MessageType.REGULAR_TEXT_MESSAGE, diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ReadStatus.java b/app/src/main/java/com/nextcloud/talk/models/json/chat/ReadStatus.java new file mode 100644 index 000000000..03442f3d1 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ReadStatus.java @@ -0,0 +1,25 @@ +/* + * Nextcloud Talk application + * + * @author Tobias Kaminsky + * Copyright (C) 2020 Tobias Kaminsky + * + * 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.chat; + +public enum ReadStatus { + NONE, SENT, READ +} 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 4f0447727..3d8bb1d3f 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java @@ -225,6 +225,10 @@ public class ApiUtils { return baseUrl + ocsApiVersion + "/cloud/user"; } + public static String getUrlForUserSettings(String baseUrl) { + return baseUrl + ocsApiVersion + spreedApiVersion + "/settings/user"; + } + public static String getUrlPostfixForStatus() { return "/status.php"; } diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java index f6e4679eb..8595bb145 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java +++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java @@ -302,6 +302,17 @@ public interface AppPreferences { @KeyByString("phone_book_integration_last_run") long getPhoneBookIntegrationLastRun(Long defaultValue); + @KeyByResource(R.string.nc_settings_read_privacy_key) + void setReadPrivacy(boolean value); + + @KeyByResource(R.string.nc_settings_read_privacy_key) + @RegisterChangeListenerMethod + void registerReadPrivacyChangeListener(OnPreferenceValueChangedListener listener); + + @KeyByResource(R.string.nc_settings_read_privacy_key) + @UnregisterChangeListenerMethod + void unregisterReadPrivacyChangeListener(OnPreferenceValueChangedListener listener); + @ClearMethod void clear(); } diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml new file mode 100644 index 000000000..70b9f2419 --- /dev/null +++ b/app/src/main/res/drawable/ic_check.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_check_all.xml b/app/src/main/res/drawable/ic_check_all.xml new file mode 100644 index 000000000..9254db10a --- /dev/null +++ b/app/src/main/res/drawable/ic_check_all.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/app/src/main/res/layout/controller_settings.xml b/app/src/main/res/layout/controller_settings.xml index a7a09c086..c96551fef 100644 --- a/app/src/main/res/layout/controller_settings.xml +++ b/app/src/main/res/layout/controller_settings.xml @@ -226,6 +226,14 @@ apc:mp_key="@string/nc_settings_phone_book_integration_key" apc:mp_summary="@string/nc_settings_phone_book_integration_desc" apc:mp_title="@string/nc_settings_phone_book_integration_title" /> + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 33960b131..8091ebe9f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -115,7 +115,10 @@ Show link previews Allows previews of content from received links for supported services link_previews + read_privacy Tap to unlock + Share my read-status and show the read-status of others + Read status 30 seconds 1 minute