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