diff --git a/app/src/main/java/com/nmc/android/ui/CommentsActionsBottomSheetDialog.kt b/app/src/main/java/com/nmc/android/ui/CommentsActionsBottomSheetDialog.kt
new file mode 100644
index 000000000000..af1891388176
--- /dev/null
+++ b/app/src/main/java/com/nmc/android/ui/CommentsActionsBottomSheetDialog.kt
@@ -0,0 +1,51 @@
+package com.nmc.android.ui
+
+import android.content.Context
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.owncloud.android.databinding.CommentsActionsBottomSheetFragmentBinding
+import com.owncloud.android.operations.comments.Comments
+
+
+class CommentsActionsBottomSheetDialog(context: Context,
+ private val comments: Comments,
+ private val commentsBottomSheetActions: CommentsBottomSheetActions) : BottomSheetDialog(context) {
+
+ private lateinit var binding: CommentsActionsBottomSheetFragmentBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding = CommentsActionsBottomSheetFragmentBinding.inflate(layoutInflater)
+
+ setContentView(binding.root)
+
+ window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+
+ setOnShowListener {
+ BottomSheetBehavior.from(binding.root.parent as View)
+ .setPeekHeight(binding.root.measuredHeight)
+ }
+
+
+ binding.menuEditComment.setOnClickListener {
+ commentsBottomSheetActions.onUpdateComment(comments)
+ dismiss()
+ }
+
+ binding.menuDeleteComment.setOnClickListener {
+ commentsBottomSheetActions.onDeleteComment(comments)
+ dismiss()
+ }
+
+
+ }
+
+ interface CommentsBottomSheetActions {
+ fun onUpdateComment(comments: Comments)
+ fun onDeleteComment(comments: Comments)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/owncloud/android/operations/comments/Comments.kt b/app/src/main/java/com/owncloud/android/operations/comments/Comments.kt
new file mode 100644
index 000000000000..b86185cf6797
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/operations/comments/Comments.kt
@@ -0,0 +1,63 @@
+package com.owncloud.android.operations.comments
+
+/**
+ * response from the Get comments api
+ *
+ *
+ *
+ * /remote.php/dav/comments/files/581625/
+ *
+ *
+ *
+ *
+ *
+ * Wed, 05 Oct 2022 07:54:20 GMT
+ *
+ * HTTP/1.1 200 OK
+ *
+ *
+ *
+ * /remote.php/dav/comments/files/581625/99
+ *
+ *
+ *
+ * 99
+ * 0
+ * 0
+ * 0
+ * Cghjgrrg
+ * comment
+ * users
+ * 120049010000000010088671
+ * Wed, 05 Oct 2022 07:54:20 GMT
+ *
+ * files
+ * 581625
+ *
+ *
+ * Dev.Kumar
+ *
+ * false
+ *
+ * HTTP/1.1 200 OK
+ *
+ *
+ *
+ */
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+import java.util.*
+
+@Parcelize
+data class Comments(val path: String,
+ val commentId: Int,
+ val message: String,
+ val actorId: String,
+ val actorDisplayName: String,
+ val actorType: String,
+ val creationDateTime: Date? = null,
+ val isUnread: Boolean = false,
+ val objectId: String,
+ val objectType: String,
+ val verb: String) : Parcelable
\ No newline at end of file
diff --git a/app/src/main/java/com/owncloud/android/operations/comments/DeleteCommentRemoteOperation.java b/app/src/main/java/com/owncloud/android/operations/comments/DeleteCommentRemoteOperation.java
new file mode 100644
index 000000000000..589ceaef25e4
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/operations/comments/DeleteCommentRemoteOperation.java
@@ -0,0 +1,82 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author TSI-mc Copyright (C) 2021 TSI-mc
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation.
+ *
+ * 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.owncloud.android.operations.comments;
+
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.utils.Log_OC;
+
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.DeleteMethod;
+
+/**
+ * class to delete the comment
+ *
+ * API : //DELETE to dav/comments/files/{file_id}/{comment_id}
+ */
+public class DeleteCommentRemoteOperation extends RemoteOperation {
+
+ private static final String TAG = DeleteCommentRemoteOperation.class.getSimpleName();
+
+ private final long fileId;
+ private final int commentId;
+
+ public DeleteCommentRemoteOperation(long fileId, int commentId) {
+ this.fileId = fileId;
+ this.commentId = commentId;
+ }
+
+ @Override
+ protected RemoteOperationResult run(OwnCloudClient client) {
+ RemoteOperationResult result;
+ int status;
+
+ DeleteMethod deleteMethod = null;
+
+ try {
+ //Delete Method
+ deleteMethod = new DeleteMethod(client.getCommentsUri(fileId) + "/" + commentId);
+
+ status = client.executeMethod(deleteMethod);
+
+ if (isSuccess(status)) {
+ result = new RemoteOperationResult<>(true, status, deleteMethod.getResponseHeaders());
+ return result;
+ } else {
+ result = new RemoteOperationResult<>(false, deleteMethod);
+ }
+
+ } catch (Exception e) {
+ result = new RemoteOperationResult<>(e);
+ Log_OC.e(TAG, "Exception while deleting comment", e);
+
+ } finally {
+ if (deleteMethod != null) {
+ deleteMethod.releaseConnection();
+ }
+ }
+ return result;
+ }
+
+ private boolean isSuccess(int status) {
+ return status == HttpStatus.SC_OK
+ || status == HttpStatus.SC_NO_CONTENT
+ || status == HttpStatus.SC_MULTI_STATUS;
+ }
+
+}
diff --git a/app/src/main/java/com/owncloud/android/operations/comments/GetCommentsRemoteOperation.java b/app/src/main/java/com/owncloud/android/operations/comments/GetCommentsRemoteOperation.java
new file mode 100644
index 000000000000..4dd14db1857e
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/operations/comments/GetCommentsRemoteOperation.java
@@ -0,0 +1,217 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author TSI-mc Copyright (C) 2021 TSI-mc
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation.
+ *
+ * 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.owncloud.android.operations.comments;
+
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.network.WebdavEntry;
+import com.owncloud.android.lib.common.network.WebdavUtils;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.utils.Log_OC;
+
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.jackrabbit.webdav.MultiStatus;
+import org.apache.jackrabbit.webdav.MultiStatusResponse;
+import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
+import org.apache.jackrabbit.webdav.property.DavProperty;
+import org.apache.jackrabbit.webdav.property.DavPropertySet;
+import org.apache.jackrabbit.webdav.xml.Namespace;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * class to fetch the list of comments for the given fileId
+ *
+ * API : //PROPFIND to dav/comments/files/{file_id}
+ */
+public class GetCommentsRemoteOperation extends RemoteOperation {
+
+ private static final String TAG = GetCommentsRemoteOperation.class.getSimpleName();
+
+ private static final String EXTENDED_PROPERTY_ID = "id";
+ protected static final String EXTENDED_PROPERTY_MESSAGE = "message";
+ private static final String EXTENDED_PROPERTY_ACTOR_DISPLAY_NAME = "actorDisplayName";
+ private static final String EXTENDED_PROPERTY_ACTOR_ID = "actorId";
+ private static final String EXTENDED_PROPERTY_ACTOR_TYPE = "actorType";
+ private static final String EXTENDED_PROPERTY_CREATION_DATE_TIME = "creationDateTime";
+ private static final String EXTENDED_PROPERTY_IS_UNREAD = "isUnread";
+ private static final String EXTENDED_PROPERTY_OBJECT_ID = "objectId";
+ private static final String EXTENDED_PROPERTY_OBJECT_TYPE = "objectType";
+ private static final String EXTENDED_PROPERTY_VERB = "verb";
+
+ private static final int CODE_PROP_SUCCESS = 200;
+ private static final int CODE_PROP_NOT_FOUND = 404;
+
+ private final long fileId;
+ private final int limit, offset;
+
+ // TODO: 10/15/22 Add pagination
+ public GetCommentsRemoteOperation(long fileId, int limit, int offset) {
+ this.fileId = fileId;
+ this.limit = limit;
+ this.offset = offset;
+ }
+
+ @Override
+ protected RemoteOperationResult run(OwnCloudClient client) {
+ PropFindMethod propFind = null;
+ RemoteOperationResult result = null;
+ try {
+ propFind = new PropFindMethod(client.getCommentsUri(fileId));
+ int status = client.executeMethod(propFind);
+
+ if (status == HttpStatus.SC_MULTI_STATUS || status == HttpStatus.SC_OK) {
+ MultiStatus dataInServer = propFind.getResponseBodyAsMultiStatus();
+
+ result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.OK);
+ result.setResultData(parseComments(dataInServer));
+
+ }
+
+ if (status == HttpStatus.SC_NOT_FOUND) {
+ result = new RemoteOperationResult(RemoteOperationResult.ResultCode.FILE_NOT_FOUND);
+ }
+
+ } catch (Exception e) {
+ Log_OC.e(TAG, "Error while retrieving comments");
+ result = new RemoteOperationResult(e);
+ } finally {
+ if (propFind != null) {
+ propFind.releaseConnection();
+ }
+ }
+
+ return result;
+
+ }
+
+ private List parseComments(MultiStatus dataInServer) {
+ List commentsList = new ArrayList<>();
+
+ Namespace ocNamespace = Namespace.getNamespace(WebdavEntry.NAMESPACE_OC);
+
+ for (MultiStatusResponse statusResponse : dataInServer.getResponses()) {
+
+ int status = statusResponse.getStatus()[0].getStatusCode();
+ if (status == CODE_PROP_NOT_FOUND) {
+ status = statusResponse.getStatus()[1].getStatusCode();
+ }
+
+ if (status != CODE_PROP_SUCCESS) {
+ continue;
+ }
+
+ DavPropertySet propSet = statusResponse.getProperties(status);
+
+ if (propSet == null) {
+ continue;
+ }
+
+ String path = statusResponse.getHref();
+
+ // OC id property
+ DavProperty> prop = propSet.get(EXTENDED_PROPERTY_ID, ocNamespace);
+ int commentId = 0;
+ if (prop != null) {
+ String id = (String) prop.getValue();
+ if (id != null) {
+ commentId = Integer.parseInt(id);
+ }
+ }
+
+ //don't look for other elements if commentId is missing or zero
+ if (commentId == 0) continue;
+
+ // OC message property
+ prop = propSet.get(EXTENDED_PROPERTY_MESSAGE, ocNamespace);
+ String message = "";
+ if (prop != null) {
+ message = (String) prop.getValue();
+ }
+
+ // OC actorId property
+ prop = propSet.get(EXTENDED_PROPERTY_ACTOR_ID, ocNamespace);
+ String actorId = "";
+ if (prop != null) {
+ actorId = (String) prop.getValue();
+ }
+
+ // OC actorDisplayName property
+ prop = propSet.get(EXTENDED_PROPERTY_ACTOR_DISPLAY_NAME, ocNamespace);
+ String actorDisplayName = "";
+ if (prop != null) {
+ actorDisplayName = (String) prop.getValue();
+ }
+
+ // OC actorType property
+ prop = propSet.get(EXTENDED_PROPERTY_ACTOR_TYPE, ocNamespace);
+ String actorType = "";
+ if (prop != null) {
+ actorType = (String) prop.getValue();
+ }
+
+ // OC creationDateTime property
+ prop = propSet.get(EXTENDED_PROPERTY_CREATION_DATE_TIME, ocNamespace);
+ Date creationDateTime = null;
+ if (prop != null) {
+ creationDateTime = WebdavUtils.parseResponseDate((String) prop.getValue());
+ }
+
+ // OC isUnread property
+ prop = propSet.get(EXTENDED_PROPERTY_IS_UNREAD, ocNamespace);
+ boolean isUnread = false;
+ if (prop != null) {
+ String value = (String) prop.getValue();
+ if (value != null) {
+ isUnread = Boolean.parseBoolean(value);
+ }
+ }
+
+ // OC objectId property
+ prop = propSet.get(EXTENDED_PROPERTY_OBJECT_ID, ocNamespace);
+ String objectId = "";
+ if (prop != null) {
+ objectId = (String) prop.getValue();
+ }
+
+ // OC objectType property
+ prop = propSet.get(EXTENDED_PROPERTY_OBJECT_TYPE, ocNamespace);
+ String objectType = "";
+ if (prop != null) {
+ objectType = (String) prop.getValue();
+ }
+
+ // OC verb property
+ prop = propSet.get(EXTENDED_PROPERTY_VERB, ocNamespace);
+ String verb = "";
+ if (prop != null) {
+ verb = (String) prop.getValue();
+ }
+
+ Comments comments = new Comments(path, commentId, message, actorId,
+ actorDisplayName, actorType, creationDateTime,
+ isUnread, objectId, objectType, verb);
+
+ commentsList.add(comments);
+ }
+
+ return commentsList;
+ }
+
+}
diff --git a/app/src/main/java/com/owncloud/android/operations/comments/UpdateCommentRemoteOperation.java b/app/src/main/java/com/owncloud/android/operations/comments/UpdateCommentRemoteOperation.java
new file mode 100644
index 000000000000..8004b62aa63d
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/operations/comments/UpdateCommentRemoteOperation.java
@@ -0,0 +1,93 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author TSI-mc Copyright (C) 2021 TSI-mc
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation.
+ *
+ * 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.owncloud.android.operations.comments;
+
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.network.WebdavEntry;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.jackrabbit.webdav.client.methods.PropPatchMethod;
+import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
+import org.apache.jackrabbit.webdav.property.DavPropertySet;
+import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
+import org.apache.jackrabbit.webdav.xml.Namespace;
+
+import java.io.IOException;
+
+/**
+ * class to update the comment
+ *
+ * API : //PROPPATCH to dav/comments/files/{file_id}/{comment_id}
+ */
+public class UpdateCommentRemoteOperation extends RemoteOperation {
+
+ private static final String TAG = UpdateCommentRemoteOperation.class.getSimpleName();
+
+ private final long fileId;
+ private final int commentId;
+ private final String message;
+
+ public UpdateCommentRemoteOperation(long fileId, int commentId, String message) {
+ this.fileId = fileId;
+ this.commentId = commentId;
+ this.message = message;
+ }
+
+ @Override
+ protected RemoteOperationResult run(OwnCloudClient client) {
+ RemoteOperationResult result;
+ PropPatchMethod propPatchMethod = null;
+
+ DavPropertySet newProps = new DavPropertySet();
+ DavPropertyNameSet removeProperties = new DavPropertyNameSet();
+
+ DefaultDavProperty messageDavProperty = new DefaultDavProperty<>(GetCommentsRemoteOperation.EXTENDED_PROPERTY_MESSAGE, message,
+ Namespace.getNamespace(WebdavEntry.NAMESPACE_OC));
+ newProps.add(messageDavProperty);
+
+ String commentsPath = client.getCommentsUri(fileId) + "/" + commentId;
+
+ try {
+ propPatchMethod = new PropPatchMethod(commentsPath, newProps, removeProperties);
+ int status = client.executeMethod(propPatchMethod);
+
+ if (isSuccess(status)) {
+ result = new RemoteOperationResult(true, status, propPatchMethod.getResponseHeaders());
+ } else {
+ client.exhaustResponse(propPatchMethod.getResponseBodyAsStream());
+ result = new RemoteOperationResult(false, status, propPatchMethod.getResponseHeaders());
+ }
+ } catch (IOException e) {
+ result = new RemoteOperationResult(e);
+ } finally {
+ if (propPatchMethod != null) {
+ propPatchMethod.releaseConnection();
+ }
+ }
+
+ return result;
+ }
+
+ private boolean isSuccess(int status) {
+ return status == HttpStatus.SC_OK
+ || status == HttpStatus.SC_NO_CONTENT
+ || status == HttpStatus.SC_MULTI_STATUS;
+ }
+
+}
diff --git a/app/src/main/java/com/owncloud/android/services/OperationsService.java b/app/src/main/java/com/owncloud/android/services/OperationsService.java
index c328008b7b19..adb37cd65619 100644
--- a/app/src/main/java/com/owncloud/android/services/OperationsService.java
+++ b/app/src/main/java/com/owncloud/android/services/OperationsService.java
@@ -52,6 +52,7 @@
import com.owncloud.android.lib.resources.shares.ShareType;
import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation;
import com.owncloud.android.operations.CheckCurrentCredentialsOperation;
+import com.owncloud.android.operations.CommentFileOperation;
import com.owncloud.android.operations.CopyFileOperation;
import com.owncloud.android.operations.CreateFolderOperation;
import com.owncloud.android.operations.CreateShareViaLinkOperation;
@@ -69,6 +70,7 @@
import com.owncloud.android.operations.UpdateSharePermissionsOperation;
import com.owncloud.android.operations.UpdateShareViaLinkOperation;
import com.owncloud.android.operations.albums.CopyFileToAlbumOperation;
+import com.owncloud.android.operations.comments.GetCommentsRemoteOperation;
import java.io.IOException;
import java.util.Optional;
@@ -112,6 +114,7 @@ public class OperationsService extends Service {
public static final String EXTRA_FILES_DOWNLOAD_LIMIT = "FILES_DOWNLOAD_LIMIT";
public static final String EXTRA_SHARE_ATTRIBUTES = "SHARE_ATTRIBUTES";
public static final String EXTRA_CREATE_ALBUM_SHARE = "CREATE_ALBUM_SHARE";
+ public static final String EXTRA_FILE_ID = "FILE_ID";
public static final String ACTION_CREATE_SHARE_VIA_LINK = "CREATE_SHARE_VIA_LINK";
public static final String ACTION_CREATE_SECURE_FILE_DROP = "CREATE_SECURE_FILE_DROP";
@@ -139,6 +142,7 @@ public class OperationsService extends Service {
public static final String ACTION_RENAME_ALBUM = "RENAME_ALBUM";
public static final String ACTION_REMOVE_ALBUM = "REMOVE_ALBUM";
public static final String ACTION_PUBLIC_SHARE_LINK_ALBUM = "PUBLIC_SHARE_LINK_ALBUM";
+ public static final String ACTION_GET_COMMENTS = "GET_COMMENTS";
private ServiceHandler mOperationsHandler;
private OperationsServiceBinder mOperationsBinder;
@@ -826,6 +830,15 @@ private Pair newOperation(Intent operationIntent) {
operation = new PublicShareLinkAlbumRemoteOperation(albmName, isCreateShare);
break;
+ case ACTION_GET_COMMENTS:
+ long fileId = operationIntent.getLongExtra(EXTRA_FILE_ID, 0L);
+ if (fileId > 0) {
+ operation = new GetCommentsRemoteOperation(fileId, 0, 0);
+ } else {
+ Log_OC.d(TAG, "Get Comments: empty or null fileId.");
+ }
+ break;
+
default:
// do nothing
break;
diff --git a/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityAndVersionListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityAndVersionListAdapter.kt
index cd2c24c65ce3..f5ab0752ab46 100644
--- a/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityAndVersionListAdapter.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityAndVersionListAdapter.kt
@@ -17,6 +17,7 @@ import com.nextcloud.common.NextcloudClient
import com.owncloud.android.databinding.VersionListItemBinding
import com.owncloud.android.lib.resources.activities.model.Activity
import com.owncloud.android.lib.resources.files.model.FileVersion
+import com.owncloud.android.operations.comments.Comments
import com.owncloud.android.ui.interfaces.ActivityListInterface
import com.owncloud.android.ui.interfaces.VersionListInterface
import com.owncloud.android.utils.DisplayUtils
@@ -28,8 +29,9 @@ class ActivityAndVersionListAdapter(
currentAccountProvider: CurrentAccountProvider,
activityListInterface: ActivityListInterface,
private val versionListInterface: VersionListInterface.View,
- viewThemeUtils: ViewThemeUtils
-) : ActivityListAdapter(context, currentAccountProvider, activityListInterface, true, viewThemeUtils) {
+ viewThemeUtils: ViewThemeUtils,
+ userId: String
+) : ActivityListAdapter(context, currentAccountProvider, activityListInterface, true, viewThemeUtils, userId) {
@SuppressLint("NotifyDataSetChanged")
fun setActivityAndVersionItems(items: MutableList, newClient: NextcloudClient?, clear: Boolean) {
@@ -58,6 +60,7 @@ class ActivityAndVersionListAdapter(
private fun Any?.timestamp(): Long? = when (this) {
is Activity -> datetime.time
is FileVersion -> modifiedTimestamp
+ is Comments -> creationDateTime?.time
else -> null
}
@@ -82,6 +85,7 @@ class ActivityAndVersionListAdapter(
override fun getItemViewType(position: Int) = when (values[position]) {
is Activity -> ACTIVITY_TYPE
is FileVersion -> VERSION_TYPE
+ is Comments -> COMMENT_TYPE
else -> HEADER_TYPE
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityListAdapter.kt
index 4aa0874ba9db..18787d1b3f23 100644
--- a/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityListAdapter.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityListAdapter.kt
@@ -8,6 +8,7 @@ package com.owncloud.android.ui.activities.adapter
import android.content.Context
import android.text.SpannableStringBuilder
+import android.text.TextUtils
import android.text.format.DateFormat
import android.text.format.DateUtils
import android.view.LayoutInflater
@@ -31,12 +32,14 @@ import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.databinding.ActivityListItemBinding
import com.owncloud.android.databinding.ActivityListItemHeaderBinding
+import com.owncloud.android.databinding.CommentListItemBinding
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.activities.model.Activity
import com.owncloud.android.lib.resources.activities.model.RichElement
import com.owncloud.android.lib.resources.activities.model.RichObject
import com.owncloud.android.lib.resources.activities.models.PreviewObject
+import com.owncloud.android.operations.comments.Comments
import com.owncloud.android.ui.activities.StickyHeaderAdapter
import com.owncloud.android.ui.interfaces.ActivityListInterface
import com.owncloud.android.utils.DisplayUtils
@@ -56,7 +59,9 @@ open class ActivityListAdapter(
private val currentAccountProvider: CurrentAccountProvider,
private val activityListInterface: ActivityListInterface,
private val isDetailView: Boolean,
- private val viewThemeUtils: ViewThemeUtils
+ private val viewThemeUtils: ViewThemeUtils,
+ // it will be null if coming from activities
+ private val userId: String? = null
) : RecyclerView.Adapter(),
StickyHeaderAdapter {
@@ -84,7 +89,9 @@ open class ActivityListAdapter(
modificationTimestamp,
DateUtils.DAY_IN_MILLIS,
DateUtils.WEEK_IN_MILLIS,
- 0
+ 0,
+ // NMC: true to avoid creating wrong header date if date is 1sec future in case of comments
+ true
)
} else {
DateFormat.format(
@@ -99,6 +106,8 @@ open class ActivityListAdapter(
val inflater = LayoutInflater.from(parent.context)
return if (viewType == ACTIVITY_TYPE) {
ActivityViewHolder(ActivityListItemBinding.inflate(inflater, parent, false))
+ } else if (viewType == COMMENT_TYPE) {
+ CommentViewHolder(CommentListItemBinding.inflate(inflater, parent, false))
} else {
ActivityViewHeaderHolder(ActivityListItemHeaderBinding.inflate(inflater, parent, false))
}
@@ -107,11 +116,15 @@ open class ActivityListAdapter(
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ActivityViewHolder -> bindActivityViewHolder(holder, position)
+ is CommentViewHolder -> bindCommentViewHolder(holder, position)
is ActivityViewHeaderHolder -> holder.binding.header.text = values[position] as String
}
}
- override fun getItemViewType(position: Int) = if (values[position] is Activity) ACTIVITY_TYPE else HEADER_TYPE
+ override fun getItemViewType(position: Int) =
+ if (values[position] is Activity) ACTIVITY_TYPE
+ else if (values[position] is Comments) COMMENT_TYPE
+ else HEADER_TYPE
override fun getItemCount() = values.size
@@ -238,6 +251,41 @@ open class ActivityListAdapter(
.getNextcloudClientFor(currentAccountProvider.user.toOwnCloudAccount(), context)
}.also { cachedNextcloudClient = it }
+ private fun bindCommentViewHolder(holder: CommentViewHolder, position: Int){
+ val comments = values[position] as Comments
+
+ if (comments.creationDateTime != null) {
+ val date = DisplayUtils.getRelativeDateTimeString(context, comments.creationDateTime.time)
+ holder.binding.datetime.text = date
+ holder.binding.datetime.visibility = View.VISIBLE
+ } else {
+ holder.binding.datetime.visibility = View.GONE
+ }
+
+ if (!TextUtils.isEmpty(comments.actorDisplayName)) {
+ holder.binding.subject.visibility = View.VISIBLE
+ holder.binding.subject.text = comments.actorDisplayName
+ } else {
+ holder.binding.subject.visibility = View.GONE
+ }
+
+ if (!TextUtils.isEmpty(comments.message)) {
+ holder.binding.message.text = comments.message
+ holder.binding.message.visibility = View.VISIBLE
+ } else {
+ holder.binding.message.visibility = View.GONE
+ }
+
+ if (!TextUtils.isEmpty(comments.actorId) && userId != null && comments.actorId == userId) {
+ holder.binding.overflowMenu.visibility = View.VISIBLE
+ holder.binding.overflowMenu.setOnClickListener({ _ ->
+ activityListInterface.onCommentsOverflowMenuClicked(
+ comments
+ )
+ })
+ }
+ }
+
private fun loadImageAsync(url: String, imageView: ImageView, @DrawableRes placeholder: Int) {
context.lifecycleScope.launch {
runCatching {
@@ -299,12 +347,16 @@ open class ActivityListAdapter(
protected class ActivityViewHolder(val binding: ActivityListItemBinding) :
RecyclerView.ViewHolder(binding.root)
+ protected class CommentViewHolder (val binding: CommentListItemBinding ) :
+ RecyclerView.ViewHolder(binding.root)
+
protected class ActivityViewHeaderHolder(val binding: ActivityListItemHeaderBinding) :
RecyclerView.ViewHolder(binding.root)
companion object {
const val HEADER_TYPE = 100
const val ACTIVITY_TYPE = 101
+ const val COMMENT_TYPE = 103
private const val COLORED_ICON_SUFFIX = "-color.svg"
private const val TIME_PATTERN = "HH:mm"
private const val HEADER_DATE_SKELETON = "EEEE, MMMM d"
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt
index 8f14a14ee523..b63e57e86ae3 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt
@@ -1989,7 +1989,8 @@ class FileDisplayActivity :
* @param file [OCFile] whose details will be shown
*/
override fun showDetails(file: OCFile?) {
- showDetails(file, 0)
+ // NMC: use 1 as activeTab
+ showDetails(file, 1)
}
/**
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.kt
index b60f3997eb6d..c558e63a69c5 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.kt
@@ -112,7 +112,7 @@ class ShareActivity :
}
override fun onShareProcessClosed() {
- finish()
+ // nothing to do here
}
private fun setupHeader(binding: ShareActivityBinding, file: OCFile, user: User) {
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.kt
index 7633297ce066..b614aa88fd2a 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.kt
@@ -25,8 +25,9 @@ class FileDetailTabAdapter(
) : FragmentStateAdapter(fragmentActivity) {
private enum class Tab(val position: Int) {
- Activities(0),
- Sharing(1),
+ Activities(1),
+ // NMC: Sharing will be 1st tab and comments will be 2nd tab
+ Sharing(0),
Details(2)
}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt
index dd8dd77020fd..de408c482718 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt
@@ -262,6 +262,12 @@ class OCFileListDelegate(
}
private fun bindUnreadComments(file: OCFile, gridViewHolder: ListViewHolder) {
+ //NMC: no need to show comment icon in grid view
+ if (gridView) {
+ gridViewHolder.unreadComments.visibility = View.GONE
+ return
+ }
+
if (file.unreadCommentsCount > 0) {
gridViewHolder.unreadComments.visibility = View.VISIBLE
gridViewHolder.unreadComments.setOnClickListener {
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/EditCommentDialogFragment.java b/app/src/main/java/com/owncloud/android/ui/dialog/EditCommentDialogFragment.java
new file mode 100644
index 000000000000..6fe67b0a1bff
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/EditCommentDialogFragment.java
@@ -0,0 +1,181 @@
+package com.owncloud.android.ui.dialog;
+
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Button;
+
+import com.owncloud.android.R;
+import com.owncloud.android.databinding.NoteDialogBinding;
+import com.owncloud.android.operations.comments.Comments;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.theme.ThemeColorUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
+
+/**
+ * Dialog to edit comment for a file
+ */
+public class EditCommentDialogFragment extends DialogFragment implements DialogInterface.OnClickListener {
+
+ private static final String ARG_COMMENT = "COMMENT";
+
+ public static final String EDIT_COMMENT_FRAGMENT_TAG = "EDIT_COMMENT_FRAGMENT";
+
+ private Comments comment;
+ private NoteDialogBinding binding;
+ private Button positiveButton;
+ private OnEditCommentListener onEditCommentListener;
+
+ public static EditCommentDialogFragment newInstance(Comments comment) {
+ EditCommentDialogFragment frag = new EditCommentDialogFragment();
+
+ Bundle args = new Bundle();
+ args.putParcelable(ARG_COMMENT, comment);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ public void setOnEditCommentListener(OnEditCommentListener onEditCommentListener) {
+ this.onEditCommentListener = onEditCommentListener;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ AlertDialog alertDialog = (AlertDialog) getDialog();
+
+ if (alertDialog != null) {
+ positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ }
+
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (getArguments() == null) {
+ throw new IllegalArgumentException("Arguments may not be null");
+ }
+ comment = getArguments().getParcelable(ARG_COMMENT);
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ //int primaryColor = ThemeColorUtils.primaryColor(getContext());
+
+ // Inflate the layout for the dialog
+ LayoutInflater inflater = requireActivity().getLayoutInflater();
+ binding = NoteDialogBinding.inflate(inflater, null, false);
+ View view = binding.getRoot();
+
+ // Setup layout
+ binding.noteContainer.setHint(requireContext().getResources().getString(R.string.new_comment));
+ binding.noteText.setText(comment.getMessage());
+ binding.noteText.requestFocus();
+ // ThemeTextInputUtils.colorTextInput(binding.noteContainer, binding.noteText, primaryColor, ThemeColorUtils.primaryAccentColor(getContext()));
+ //binding.noteText.setHighlightColor(getResources().getColor(R.color.et_highlight_color));
+ binding.noteContainer.setDefaultHintTextColor(new ColorStateList(
+ new int[][]{
+ new int[]{-android.R.attr.state_focused},
+ new int[]{android.R.attr.state_focused},
+ },
+ new int[]{
+ Color.GRAY,
+ getResources().getColor(R.color.text_color)
+ }
+ ));
+
+ binding.noteText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ /**
+ * When user enters a same message or empty message
+ */
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ String message = "";
+ if (binding.noteText.getText() != null) {
+ message = binding.noteText.getText().toString().trim();
+ }
+
+ if (TextUtils.isEmpty(message)) {
+ binding.noteContainer.setError(getText(R.string.empty_comment_message));
+ positiveButton.setEnabled(false);
+ } else if (binding.noteContainer.getError() != null) {
+ binding.noteContainer.setError(null);
+ // Called to remove extra padding
+ binding.noteContainer.setErrorEnabled(false);
+ positiveButton.setEnabled(true);
+ }
+ }
+ });
+
+
+ // Build the dialog
+ AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
+ builder.setView(view)
+ .setPositiveButton(R.string.done, this)
+ .setNeutralButton(R.string.common_cancel, this)
+ .setTitle(R.string.edit_comment);
+ Dialog dialog = builder.create();
+
+ Window window = dialog.getWindow();
+
+ if (window != null) {
+ window.setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+ }
+
+ return dialog;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == AlertDialog.BUTTON_POSITIVE) {
+ String message = "";
+
+ if (binding.noteText.getText() != null) {
+ message = binding.noteText.getText().toString().trim();
+ }
+
+ if (onEditCommentListener != null) {
+ onEditCommentListener.doUpdateComment(comment, message);
+ } else {
+ DisplayUtils.showSnackMessage(requireActivity(), R.string.error_comment_update);
+ }
+
+ }
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+
+ public interface OnEditCommentListener {
+ void doUpdateComment(Comments comments, String message);
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.kt b/app/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.kt
index 1ea754c42ae1..2c888c948447 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.kt
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.kt
@@ -206,7 +206,8 @@ class SendShareDialog :
dismiss()
if (activity is FileDisplayActivity) {
- (activity as FileDisplayActivity?)?.showDetails(file, 1)
+ // NMC: use 0 as activeTab
+ (activity as FileDisplayActivity?)?.showDetails(file, 0)
} else {
fileOperationsHelper?.showShareFile(file)
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/ActivitiesFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/ActivitiesFragment.kt
index 53fed3aad51f..ab145f35e35b 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/ActivitiesFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/ActivitiesFragment.kt
@@ -27,6 +27,7 @@ import com.owncloud.android.lib.resources.files.FileUtils
import com.owncloud.android.ui.activities.ActivitiesContract
import com.owncloud.android.ui.activities.ActivitiesPresenter
import com.owncloud.android.ui.activities.adapter.ActivityListAdapter
+import com.owncloud.android.operations.comments.Comments
import com.owncloud.android.ui.activities.data.activities.ActivitiesRepository
import com.owncloud.android.ui.activities.data.files.FilesRepository
import com.owncloud.android.ui.activity.FileActivity
@@ -134,6 +135,8 @@ class ActivitiesFragment :
actionListener?.openActivity(path, baseActivity)
}
+ override fun onCommentsOverflowMenuClicked(comments: Comments?) = Unit
+
override fun showActivities(activities: List, client: NextcloudClient, lastGiven: Long) {
val binding = binding ?: return
val clear = this.lastGiven == ActivitiesContract.ActionListener.UNDEFINED.toLong()
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.kt
index 8283e0d82439..8248e582631d 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.kt
@@ -9,21 +9,25 @@
*/
package com.owncloud.android.ui.fragment
+import android.accounts.AccountManager
import android.content.ContentResolver
import android.graphics.drawable.Drawable
import android.os.Bundle
+import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.view.inputmethod.EditorInfo
+import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.annotation.VisibleForTesting
+import androidx.appcompat.app.AlertDialog
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import com.nextcloud.client.account.User
import com.nextcloud.client.account.UserAccountManager
@@ -32,6 +36,7 @@ import com.nextcloud.client.network.ClientFactory
import com.nextcloud.client.network.ClientFactory.CreationException
import com.nextcloud.common.NextcloudClient
import com.nextcloud.utils.extensions.getParcelableArgument
+import com.nmc.android.ui.CommentsActionsBottomSheetDialog
import com.owncloud.android.R
import com.owncloud.android.databinding.FileDetailsActivitiesFragmentBinding
import com.owncloud.android.datamodel.FileDataStorageManager
@@ -45,8 +50,13 @@ import com.owncloud.android.lib.resources.comments.MarkCommentsAsReadRemoteOpera
import com.owncloud.android.lib.resources.files.ReadFileVersionsRemoteOperation
import com.owncloud.android.lib.resources.files.model.FileVersion
import com.owncloud.android.operations.CommentFileOperation
+import com.owncloud.android.operations.comments.Comments
+import com.owncloud.android.operations.comments.DeleteCommentRemoteOperation
+import com.owncloud.android.operations.comments.GetCommentsRemoteOperation
+import com.owncloud.android.operations.comments.UpdateCommentRemoteOperation
import com.owncloud.android.ui.activities.adapter.ActivityAndVersionListAdapter
import com.owncloud.android.ui.activity.ComponentsGetter
+import com.owncloud.android.ui.dialog.EditCommentDialogFragment
import com.owncloud.android.ui.events.CommentsEvent
import com.owncloud.android.ui.helpers.FileOperationsHelper
import com.owncloud.android.ui.interfaces.ActivityListInterface
@@ -54,6 +64,7 @@ import com.owncloud.android.ui.interfaces.VersionListInterface
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener
import com.owncloud.android.utils.theme.ViewThemeUtils
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
@@ -68,6 +79,7 @@ class FileDetailActivitiesFragment :
ActivityListInterface,
AvatarGenerationListener,
VersionListInterface.View,
+ CommentsActionsBottomSheetDialog.CommentsBottomSheetActions,
Injectable {
private var adapter: ActivityAndVersionListAdapter? = null
@@ -123,7 +135,19 @@ class FileDetailActivitiesFragment :
callback = createCommentCallback()
binding.submitComment.setOnClickListener { submitComment() }
- viewThemeUtils.material.colorTextInputLayout(binding.commentInputFieldContainer)
+ binding.commentInputField.setOnEditorActionListener(object : TextView.OnEditorActionListener {
+ override fun onEditorAction(
+ p0: TextView?,
+ actionId: Int,
+ p2: KeyEvent?
+ ): Boolean {
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ submitComment()
+ return true
+ }
+ return false
+ }
+ })
DisplayUtils.setAvatar(
user!!,
@@ -165,26 +189,16 @@ class FileDetailActivitiesFragment :
ResourcesCompat.getDrawable(resources, R.drawable.ic_activity, null)
)
binding.emptyList.emptyListView.visibility = View.GONE
-
- adapter = ActivityAndVersionListAdapter(requireActivity(), accountManager, this, this, viewThemeUtils)
+ val acctManager = AccountManager.get(context)
+ val userId = acctManager.getUserData(
+ user?.toPlatformAccount(),
+ com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID
+ )
+ adapter = ActivityAndVersionListAdapter(requireActivity(), accountManager, this, this, viewThemeUtils, userId)
binding.list.adapter = adapter
val layoutManager = LinearLayoutManager(context)
binding.list.layoutManager = layoutManager
- binding.list.addOnScrollListener(object : RecyclerView.OnScrollListener() {
- override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
- super.onScrolled(recyclerView, dx, dy)
-
- val visibleItemCount = recyclerView.childCount
- val totalItemCount = layoutManager.itemCount
- val firstVisibleItemIndex = layoutManager.findFirstVisibleItemPosition()
-
- val reachedEnd = (totalItemCount - visibleItemCount) <= (firstVisibleItemIndex + LOAD_MORE_THRESHOLD)
- if (!isLoadingActivities && reachedEnd && lastGiven > 0) {
- fetchAndSetData(lastGiven)
- }
- }
- })
}
private fun setupRefreshListeners(binding: FileDetailsActivitiesFragmentBinding) {
@@ -224,9 +238,74 @@ class FileDetailActivitiesFragment :
fetchAndSetData(-1)
}
+ private fun fetchAndSetData(lastGiven: Int) {
+ val activity = getActivity()
+
+ if (activity == null) {
+ Log_OC.e(this, "Activity is null, aborting!")
+ return;
+ }
+
+ val user = accountManager.user;
+
+ if (user.isAnonymous) {
+ activity.runOnUiThread {
+ setEmptyContent(getString(R.string.common_error), getString(R.string.file_detail_comment_error))
+ }
+ return
+ }
+
+ val t = Thread({
+ try {
+ ownCloudClient = clientFactory.create(user)
+ nextcloudClient = clientFactory.createNextcloudClient(user)
+
+ isLoadingActivities = true
+
+ val getCommentsRemoteOperation = GetCommentsRemoteOperation(file!!.localId, 0, 0)
+
+ Log_OC.d(TAG, "BEFORE getCommentsRemoteOperation.execute")
+ val result = getCommentsRemoteOperation.execute(ownCloudClient)
+
+
+ if (result.isSuccess && result.getResultData() != null) {
+ val commentsList = result.getResultData() as List<*>
+
+ activity.runOnUiThread({
+ if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
+ populateList(commentsList, lastGiven == -1)
+ }
+ })
+ } else {
+ Log_OC.d(TAG, result.logMessage)
+ // show error
+ var logMessage = result.logMessage
+ if (result.httpCode == HttpStatus.SC_NOT_MODIFIED) {
+ logMessage = getString(R.string.activities_no_results_message)
+ }
+ val finalLogMessage = logMessage
+ activity.runOnUiThread({
+ if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
+ setErrorContent(finalLogMessage)
+ isLoadingActivities = false
+ }
+ })
+ }
+
+ hideRefreshLayoutLoader()
+ } catch (e: CreationException) {
+ Log_OC.e(TAG, "Error fetching file details comments", e)
+ }
+ })
+
+ t.start()
+ }
+
+ // NMC: Not using this method as we don't have to show the activities
+ /* */
/**
* @param lastGiven long; -1 to disable
- */
+ *//*
@Suppress("DEPRECATION")
private fun fetchAndSetData(lastGiven: Long) {
val activity = activity
@@ -262,7 +341,7 @@ class FileDetailActivitiesFragment :
Log_OC.e(TAG, "Error fetching file details activities", e)
}
}
- }
+ }*/
@Suppress("DEPRECATION")
private fun loadActivities(
@@ -401,8 +480,8 @@ class FileDetailActivitiesFragment :
if (adapter?.itemCount == 0) {
setEmptyContent(
- getString(R.string.activities_no_results_headline),
- getString(R.string.activities_no_results_message)
+ getString(R.string.comments_no_results_headline),
+ getString(R.string.comments_no_results_message)
)
} else {
binding.swipeContainingList.visibility = View.VISIBLE
@@ -413,7 +492,8 @@ class FileDetailActivitiesFragment :
}
private fun setEmptyContent(headline: String?, message: String?) {
- setInfoContent(R.drawable.ic_activity, headline, message)
+ // NMC: no icon required for empty state
+ setInfoContent(0, headline, message)
}
@VisibleForTesting
@@ -424,9 +504,20 @@ class FileDetailActivitiesFragment :
private fun setInfoContent(@DrawableRes icon: Int, headline: String?, message: String?) {
val binding = binding ?: return
- binding.emptyList.emptyListIcon.setImageDrawable(
- ResourcesCompat.getDrawable(requireContext().resources, icon, null)
- )
+ // NMC: to handle no icon visibility
+ if (icon != 0) {
+ binding.emptyList.emptyListIcon.setImageDrawable(
+ ResourcesCompat.getDrawable(
+ requireContext().resources,
+ icon,
+ null
+ )
+ )
+ binding.emptyList.emptyListIcon.visibility = View.VISIBLE
+ } else {
+ binding.emptyList.emptyListIcon.visibility = View.GONE
+ }
+
binding.emptyList.emptyListViewHeadline.text = headline
binding.emptyList.emptyListViewText.text = message
@@ -435,7 +526,6 @@ class FileDetailActivitiesFragment :
binding.emptyList.emptyListViewHeadline.visibility = View.VISIBLE
binding.emptyList.emptyListViewText.visibility = View.VISIBLE
- binding.emptyList.emptyListIcon.visibility = View.VISIBLE
binding.emptyList.emptyListView.visibility = View.VISIBLE
binding.swipeContainingEmpty.visibility = View.VISIBLE
}
@@ -456,6 +546,12 @@ class FileDetailActivitiesFragment :
// TODO implement activity click
}
+ override fun onCommentsOverflowMenuClicked(comments: Comments?) {
+ comments?.let {
+ CommentsActionsBottomSheetDialog(requireContext(), it, this).show()
+ }
+ }
+
override fun onRestoreClicked(fileVersion: FileVersion?) {
operationsHelper?.restoreFileVersion(fileVersion)
}
@@ -472,6 +568,31 @@ class FileDetailActivitiesFragment :
isLoadingActivities = false
}
+ override fun onUpdateComment(comments: Comments) {
+ val dialog = EditCommentDialogFragment.newInstance(comments)
+ dialog.setOnEditCommentListener { comments1, message ->
+ UpdateCommentTask(message, file!!.localId, comments1.commentId, callback, ownCloudClient!!)
+ .execute(lifecycleScope)
+ }
+ dialog.show(requireActivity().supportFragmentManager, EditCommentDialogFragment.EDIT_COMMENT_FRAGMENT_TAG)
+ }
+
+ override fun onDeleteComment(comments: Comments) {
+ val builder = AlertDialog.Builder(requireActivity())
+ builder.setPositiveButton(
+ R.string.common_yes
+ ) { _, _ ->
+ DeleteCommentTask(
+ file!!.localId, comments.commentId,
+ callback, ownCloudClient!!
+ ).execute(lifecycleScope)
+ }
+ .setNegativeButton(R.string.common_no, null)
+ .setMessage(R.string.delete_comment_dialog_message);
+ val dialog = builder.create()
+ dialog.show()
+ }
+
companion object {
private val TAG: String = FileDetailActivitiesFragment::class.java.simpleName
@@ -489,4 +610,60 @@ class FileDetailActivitiesFragment :
}
}
}
+
+ class UpdateCommentTask(
+ private val message: String,
+ private val fileId: Long,
+ private val commentId: Int,
+ private val callback: VersionListInterface.CommentCallback?,
+ private val client: OwnCloudClient
+ ) {
+
+ fun execute(scope: CoroutineScope) {
+ scope.launch {
+ val success = withContext(Dispatchers.IO) {
+ val operation =
+ UpdateCommentRemoteOperation(fileId, commentId, message)
+
+ val result = operation.execute(client)
+ result.isSuccess
+ }
+
+ if (success) {
+ callback?.onSuccess()
+ // Call error to show success message
+ callback?.onError(R.string.success_update_comment_file)
+ } else {
+ callback?.onError(R.string.error_update_comment_file)
+ }
+ }
+ }
+ }
+
+ class DeleteCommentTask(
+ private val fileId: Long,
+ private val commentId: Int,
+ private val callback: VersionListInterface.CommentCallback?,
+ private val client: OwnCloudClient
+ ) {
+
+ fun execute(scope: CoroutineScope) {
+ scope.launch {
+ val success = withContext(Dispatchers.IO) {
+ val operation = DeleteCommentRemoteOperation(fileId, commentId)
+
+ val result = operation.execute(client)
+ result.isSuccess
+ }
+
+ if (success) {
+ callback?.onSuccess()
+ // Call error to show success message
+ callback?.onError(R.string.success_delete_comment_file)
+ } else {
+ callback?.onError(R.string.error_delete_comment_file)
+ }
+ }
+ }
+ }
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
index c45076cd078e..9dae96623f72 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
@@ -188,6 +188,11 @@ public FileDetailSharingFragment getFileDetailSharingFragment() {
* @return reference to the {@link FileDetailActivitiesFragment}
*/
public FileDetailActivitiesFragment getFileDetailActivitiesFragment() {
+ // NMC: uncomment below code if any crash is happening during testing
+ // else remove the code
+ /* if (binding == null) {
+ return null;
+ }*/
if (binding.pager.getAdapter() instanceof FileDetailTabAdapter adapter) {
return adapter.getFileDetailActivitiesFragment();
}
@@ -305,18 +310,14 @@ private void onOverflowIconClicked() {
private void setupViewPager() {
binding.tabLayout.removeAllTabs();
- binding.tabLayout.addTab(
- binding
- .tabLayout
- .newTab()
- .setText(R.string.drawer_item_activities)
- .setIcon(R.drawable.selector_tab_activities)
- );
-
if (showSharingTab()) {
- binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.share_dialog_title).setIcon(R.drawable.selector_tab_share));
+ // NMC: no icon required for tabs
+ binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.share_dialog_title));
}
+ // NMC: 2nd tab will be comments and without icon
+ binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.comments_tab_title));
+
if (showDetailsTab()) {
binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.filedetails_details).setIcon(R.drawable.info_24));
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt
index cdacde73a97f..d13eecf0a49f 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.kt
@@ -57,6 +57,7 @@ import com.owncloud.android.lib.resources.status.OCCapability
import com.owncloud.android.operations.RefreshFolderOperation
import com.owncloud.android.providers.UsersAndGroupsSearchConfig
import com.owncloud.android.ui.activity.FileActivity
+import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.adapter.ShareeListAdapter
import com.owncloud.android.ui.adapter.ShareeListAdapterListener
import com.owncloud.android.ui.asynctasks.RetrieveHoverCardAsyncTask
@@ -142,6 +143,8 @@ class FileDetailSharingFragment :
fetchSharees()
setupView()
+
+ showHideSharingTitle()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@@ -252,6 +255,16 @@ class FileDetailSharingFragment :
}
}
+ // if FileDetailSharingFragment is launched from OCFileListFragment(FileDisplayActivity)
+ // i.e by clicking on 3 dot menu item -> Comments
+ // then we have to hide the title
+ private fun showHideSharingTitle() {
+ if (requireActivity() is FileDisplayActivity) {
+ // need to fix when merged with sharing feature
+ // binding.sharingTitle.setVisibility(View.GONE);
+ }
+ }
+
private fun stopLoadingAnimationAndShowShareContainer() {
binding?.run {
shimmerLayout.root.run {
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt
index 067cba933bd9..8f60699461f1 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt
@@ -748,6 +748,7 @@ class FileDetailsSharingProcessFragment :
private fun removeCurrentFragment() {
onEditShareListener.onShareProcessClosed()
fileActivity?.supportFragmentManager?.beginTransaction()?.remove(this)?.commit()
+ requireActivity().supportFragmentManager.popBackStack()
}
/**
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
index 8a1c7335bdd7..05f0a0624c97 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
@@ -526,7 +526,7 @@ public void registerFabListener() {
Log_OC.w(TAG, "currentDir is null cannot open bottom sheet dialog");
return;
}
-
+
final OCFileListBottomSheetDialog dialog = new OCFileListBottomSheetDialog(fileActivity,
this,
deviceInfo,
@@ -686,7 +686,8 @@ public void createRichWorkspace() {
@Override
public void onShareIconClick(OCFile file) {
- mContainerActivity.showDetails(file, 1);
+ // NMC: use 0 as activeTab
+ mContainerActivity.showDetails(file, 0);
}
@Override
@@ -696,7 +697,8 @@ public void showShareDetailView(OCFile file) {
@Override
public void showActivityDetailView(OCFile file) {
- mContainerActivity.showDetails(file, 0);
+ // NMC: use 1 as activeTab
+ mContainerActivity.showDetails(file, 1);
}
@Override
@@ -1704,7 +1706,7 @@ private void updateLayout() {
invalidateActionMode();
}
-
+
private void updateSortButton() {
if (mSortButton != null) {
FileSortOrder sortOrder;
diff --git a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java
index a46a61f86edf..53577e61a57d 100755
--- a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java
+++ b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java
@@ -778,6 +778,18 @@ public void updateShareInformation(OCShare share,
queueShareIntent(updateShareIntent);
}
+ public void getComments(long fileId) {
+ if (fileActivity != null && fileActivity.getOperationsServiceBinder() != null) {
+ Intent commentsIntent = new Intent(fileActivity, OperationsService.class);
+ commentsIntent.setAction(OperationsService.ACTION_GET_COMMENTS);
+ commentsIntent.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount());
+ commentsIntent.putExtra(OperationsService.EXTRA_FILE_ID, fileId);
+
+ mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(commentsIntent);
+ } else {
+ Log_OC.d(TAG, "File activity or operation service binder is null.");
+ }
+ }
public void sendShareFile(OCFile file, boolean hideNcSharingOptions) {
// Show dialog
diff --git a/app/src/main/java/com/owncloud/android/ui/interfaces/ActivityListInterface.java b/app/src/main/java/com/owncloud/android/ui/interfaces/ActivityListInterface.java
index 0340f8be7000..8a4ddc8c5e60 100644
--- a/app/src/main/java/com/owncloud/android/ui/interfaces/ActivityListInterface.java
+++ b/app/src/main/java/com/owncloud/android/ui/interfaces/ActivityListInterface.java
@@ -7,8 +7,11 @@
package com.owncloud.android.ui.interfaces;
import com.owncloud.android.lib.resources.activities.model.RichObject;
+import com.owncloud.android.operations.comments.Comments;
public interface ActivityListInterface {
void onActivityClicked(RichObject richObject);
+
+ void onCommentsOverflowMenuClicked(Comments comments);
}
diff --git a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java
index c689415f3042..396b5bf7e509 100644
--- a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java
+++ b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java
@@ -67,6 +67,7 @@
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -300,6 +301,54 @@ public static CharSequence getRelativeDateTimeString(Context c,
}
}
+ /**
+ * Code from: https://stackoverflow.com/questions/35858608/how-to-convert-time-to-time-ago-in-android
+ *
+ * method convert the passed time into human readable like into seconds, minutes, days, weeks, years
+ *
+ * @param context
+ * @param time
+ * @return
+ */
+ public static String getRelativeDateTimeString(Context context, long time) {
+
+ String convTime = null;
+
+ Date nowTime = new Date();
+
+ long dateDiff = nowTime.getTime() - time;
+
+ long second = TimeUnit.MILLISECONDS.toSeconds(dateDiff);
+ long minute = TimeUnit.MILLISECONDS.toMinutes(dateDiff);
+ long hour = TimeUnit.MILLISECONDS.toHours(dateDiff);
+ long day = TimeUnit.MILLISECONDS.toDays(dateDiff);
+
+ if (second == 0) {
+ convTime = context.getResources().getString(R.string.just_now);
+ } else if (second < 60) {
+ convTime = context.getResources().getQuantityString(R.plurals.seconds_ago, (int) second, (int) second);
+ } else if (minute < 60) {
+ convTime = context.getResources().getQuantityString(R.plurals.minutes_ago, (int) minute, (int) minute);
+ } else if (hour < 24) {
+ convTime = context.getResources().getQuantityString(R.plurals.hours_ago, (int) hour, (int) hour);
+ } else if (day >= 7) {
+ if (day > 360) {
+ long year = (day / 360);
+ convTime = context.getResources().getQuantityString(R.plurals.years_ago, (int) year, (int) year);
+ } else if (day > 30) {
+ long month = (day / 30);
+ convTime = context.getResources().getQuantityString(R.plurals.months_ago, (int) month, (int) month);
+ } else {
+ long week = (day / 7);
+ convTime = context.getResources().getQuantityString(R.plurals.weeks_ago, (int) week, (int) week);
+ }
+ } else {
+ convTime = context.getResources().getQuantityString(R.plurals.days_ago, (int) day, (int) day);
+ }
+
+ return convTime;
+ }
+
/**
* Gets the screen size in pixels.
*
diff --git a/app/src/main/res/drawable/activity_list_item_header_background.xml b/app/src/main/res/drawable/activity_list_item_header_background.xml
new file mode 100644
index 000000000000..1ae933dd0a47
--- /dev/null
+++ b/app/src/main/res/drawable/activity_list_item_header_background.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_delete_comment.xml b/app/src/main/res/drawable/ic_delete_comment.xml
new file mode 100644
index 000000000000..f0dd8d6fa79c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_delete_comment.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_pencil_edit.xml b/app/src/main/res/drawable/ic_pencil_edit.xml
new file mode 100644
index 000000000000..a1089345a7b3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pencil_edit.xml
@@ -0,0 +1,12 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/share_search_background.xml b/app/src/main/res/drawable/share_search_background.xml
new file mode 100644
index 000000000000..2d8cc41165bb
--- /dev/null
+++ b/app/src/main/res/drawable/share_search_background.xml
@@ -0,0 +1,17 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_list_item_header.xml b/app/src/main/res/layout/activity_list_item_header.xml
index 79947292aedd..c43d29e7200d 100644
--- a/app/src/main/res/layout/activity_list_item_header.xml
+++ b/app/src/main/res/layout/activity_list_item_header.xml
@@ -6,19 +6,30 @@
~ SPDX-FileCopyrightText: 2017 Alejandro Morales
~ SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
-->
-
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/standard_half_margin"
+ android:background="@drawable/activity_list_item_header_background"
+ tools:text="Today"
+ android:textColor="@color/text_color"
+ android:paddingLeft="@dimen/standard_padding"
+ android:paddingTop="@dimen/standard_half_padding"
+ android:paddingRight="@dimen/standard_padding"
+ android:paddingBottom="@dimen/standard_half_padding"
+ android:textSize="@dimen/activity_list_item_title_header_text_size"
+ android:textStyle="normal"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
-
+
diff --git a/app/src/main/res/layout/comment_list_item.xml b/app/src/main/res/layout/comment_list_item.xml
new file mode 100644
index 000000000000..6dc468aac795
--- /dev/null
+++ b/app/src/main/res/layout/comment_list_item.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/comment_list_item_shimmer.xml b/app/src/main/res/layout/comment_list_item_shimmer.xml
new file mode 100644
index 000000000000..d2aaee582ea0
--- /dev/null
+++ b/app/src/main/res/layout/comment_list_item_shimmer.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/comments_actions_bottom_sheet_fragment.xml b/app/src/main/res/layout/comments_actions_bottom_sheet_fragment.xml
new file mode 100644
index 000000000000..89f789993785
--- /dev/null
+++ b/app/src/main/res/layout/comments_actions_bottom_sheet_fragment.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/file_details_activities_fragment.xml b/app/src/main/res/layout/file_details_activities_fragment.xml
index 1630392b1514..e6db54cee809 100644
--- a/app/src/main/res/layout/file_details_activities_fragment.xml
+++ b/app/src/main/res/layout/file_details_activities_fragment.xml
@@ -8,6 +8,7 @@
@@ -15,8 +16,8 @@
-
+ android:background="@drawable/share_search_background"
+ android:hint="@string/new_comment"
+ android:imeOptions="actionDone"
+ android:inputType="textNoSuggestions|textCapSentences"
+ android:paddingLeft="@dimen/standard_padding"
+ android:paddingRight="@dimen/standard_padding"
+ android:textColor="@color/text_color"
+ android:textColorHint="@color/secondary_text_color"
+ android:textSize="@dimen/txt_size_17sp" />
-
-
-
-
-
+ app:iconTint="@color/grey_60" />
@@ -69,6 +71,7 @@
android:id="@+id/swipe_containing_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/standard_half_margin"
android:footerDividersEnabled="false"
android:visibility="visible">
@@ -76,15 +79,12 @@
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginLeft="-3dp"
- android:layout_marginRight="-3dp"
- android:layout_marginBottom="-3dp"
android:background="@color/bg_default"
android:clipToPadding="false"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical"
android:visibility="visible"
- tools:listitem="@layout/activity_list_item" />
+ tools:listitem="@layout/comment_list_item" />
@@ -97,7 +97,8 @@
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/standard_half_margin">
-
+
+
+
-
+
-
+
diff --git a/app/src/main/res/layout/grid_item.xml b/app/src/main/res/layout/grid_item.xml
index 97e1084bdbd6..efccd5d8957f 100644
--- a/app/src/main/res/layout/grid_item.xml
+++ b/app/src/main/res/layout/grid_item.xml
@@ -98,7 +98,7 @@
android:clickable="true"
android:contentDescription="@string/unread_comments"
android:focusable="true"
- android:src="@drawable/ic_comment_grid"
+ android:src="@drawable/ic_comment"
android:visibility="gone"
app:tint="@color/grid_file_features_icon_color"
tools:ignore="TouchTargetSizeCheck"
diff --git a/app/src/main/res/values-de/nmc_comments_strings.xml b/app/src/main/res/values-de/nmc_comments_strings.xml
new file mode 100644
index 000000000000..fec88a1bdc94
--- /dev/null
+++ b/app/src/main/res/values-de/nmc_comments_strings.xml
@@ -0,0 +1,51 @@
+
+
+
+ Kommentar
+ Kommentar löschen
+ Kommentar bearbeiten
+ Fehler beim Laden der Kommentare
+ Noch keine Kommentare.
+ Sie können geteilte Inhalte kommentieren. Ihre Nachrichten werden alle erreichen, mit denen die Datei oder der Ordner geteilt ist.
+ Kommentarfeld darf nicht leer sein.
+ Aktualisierung fehlgeschlagen.
+ Möchten Sie den Kommentar wirklich löschen?
+ Kommentar aktualisiert
+ Fehler beim Aktualisieren
+ Kommentar gelöscht
+ Fehler beim Löschen
+ Gerade eben
+
+ - vor %d Sekunde
+ - vor %d Sekunden
+
+
+ - vor %d Minute
+ - vor %d Minuten
+
+
+ - vor %d Stunde
+ - vor %d Stunden
+
+
+ - vor %d Tag
+ - vor %d Tagen
+
+
+ - vor %d Woche
+ - vor %d Wochen
+
+
+ - vor %d Monat
+ - vor %d Monaten
+
+
+ - vor %d Jahr
+ - vor %d Jahren
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 1e55034fdcb2..bb71a053510a 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -656,7 +656,7 @@
Es ist ein Fehler beim Verbinden mit dem Server aufgetreten.
Es ist ein Fehler beim Warten auf den Server aufgetreten, die Operation konnte nicht abgeschlossen werden
Die Operation kann nicht abgeschlossen werden, der Server ist nicht erreichbar.
- Neuer Kommentar …
+ Schreiben Sie eine Nachricht…
Neuer Medienordner %1$s gefunden.
Foto
Video
@@ -1086,7 +1086,7 @@
Die Suche dauert länger als üblich. Ergebnisse werden angezeigt, sobald der Server antwortet
Unbekannt
Datei entsperren
- Es gibt ungelesene Kommentare
+ Es gibt ungelesene Kommentare
Verschlüsselung aufheben
Von Favoriten entfernen
Ordner aus der internen 2-Wege-Synchronisierung entfernen
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
index 72908c65f15b..e89ae5f818a0 100644
--- a/app/src/main/res/values-night/colors.xml
+++ b/app/src/main/res/values-night/colors.xml
@@ -39,4 +39,70 @@
@android:color/white
#2A2A2A
+
+
+ #FFFFFF
+ @color/grey_30
+ @color/grey_30
+ #CCCCCC
+ @color/grey_70
+ @color/grey_80
+ #2D2D2D
+ @color/grey_70
+ @color/grey_70
+
+
+ @color/grey_80
+ @color/grey_0
+
+
+ @color/grey_80
+ @color/grey_0
+
+
+ @color/grey_60
+ @color/grey_0
+ @color/grey_0
+ @color/grey_30
+ #FFFFFF
+ @color/grey_30
+ @color/grey_80
+ #FFFFFF
+
+
+ @color/grey_80
+ @color/grey_30
+ @color/grey_0
+
+
+ @color/grey_80
+ @color/grey_0
+ @color/grey_80
+
+
+ @color/grey_70
+ @color/grey_60
+ @color/grey_70
+ @color/grey_60
+
+
+ @color/grey_70
+ @color/grey_70
+
+
+ #FFFFFF
+ @color/grey_30
+ @color/grey_0
+ @color/grey_0
+ @color/grey_0
+ @color/grey_0
+ @color/grey_60
+ @color/grey_0
+ #FFFFFF
+
+
+ #121212
+ @color/grey_0
+ @color/grey_80
+ @color/grey_80
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index d8edd0a0e602..e849fda9c8d5 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -30,6 +30,7 @@
#757575
+ #33E20074
#222222
#EEEEEE
#BDBDBD
@@ -88,4 +89,95 @@
#A5A5A5
#EFEFEF
+
+
+ #191919
+ @color/primary
+ #191919
+ #191919
+ @color/grey_30
+ @android:color/white
+ #FFFFFF
+ @color/grey_0
+ #CCCCCC
+ #77c4ff
+ #B3FFFFFF
+ @color/grey_10
+
+
+ #101010
+ #F2F2F2
+ #E5E5E5
+ #B2B2B2
+ #666666
+ #4C4C4C
+ #333333
+
+
+ @color/design_snackbar_background_color
+ @color/white
+
+
+ #FFFFFF
+ #191919
+
+
+ @color/grey_0
+ #191919
+ @color/primary
+ #191919
+ @color/primary
+ @color/grey_30
+ @color/white
+ #191919
+
+
+ #FFFFFF
+ #191919
+ #191919
+
+
+ #FFFFFF
+ #191919
+ #FFFFFF
+
+
+ @color/primary
+ #F399C7
+ @color/grey_0
+ @color/grey_0
+ #FFFFFF
+ @color/grey_30
+ @color/grey_0
+ @color/grey_0
+
+
+ @color/primary
+ @color/grey_30
+ @color/grey_30
+ #CCCCCC
+
+
+ #191919
+ @color/grey_30
+ #191919
+ #191919
+ #191919
+ #191919
+ @color/grey_30
+ #191919
+ #000000
+ #191919
+ #F6E5EB
+ #C16F81
+ #0D39DF
+ #0099ff
+
+
+ @color/grey_0
+ #191919
+ @color/grey_0
+ @color/grey_30
+ #77b6bb
+ #5077b6bb
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 000000000000..89a1484c9e2a
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,32 @@
+
+
+ 4dp
+ 16dp
+ 24dp
+ 6dp
+ 18sp
+ 15sp
+ 15dp
+ 56dp
+ 86dp
+ 80dp
+ 11sp
+ 30dp
+ 55dp
+ 258dp
+ 17sp
+ 20dp
+ 160dp
+ 50dp
+ 150dp
+ 55dp
+ 48dp
+ 48dp
+ 24dp
+ 26dp
+ 20sp
+ 145dp
+ 1dp
+ 13sp
+ 10dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dims.xml b/app/src/main/res/values/dims.xml
index 5e8e56a0f69a..ea4f8da97e8f 100644
--- a/app/src/main/res/values/dims.xml
+++ b/app/src/main/res/values/dims.xml
@@ -110,7 +110,7 @@
16dp
24dp
-3dp
- 16sp
+ 15sp
-3dp
48dp
24dp
diff --git a/app/src/main/res/values/nmc_comments_strings.xml b/app/src/main/res/values/nmc_comments_strings.xml
new file mode 100644
index 000000000000..07c9cd07bbf6
--- /dev/null
+++ b/app/src/main/res/values/nmc_comments_strings.xml
@@ -0,0 +1,51 @@
+
+
+
+ Comments
+ Delete comment
+ Edit comment
+ Error retrieving comments for file
+ No comments yet.
+ You can comment on shared content. Your messages will reach everyone with whom the file or folder is shared.
+ Comment cannot be empty.
+ Could not update comment.
+ Are you sure you want to delete this comment?
+ Comment updated successfully
+ Error in updating comment
+ Comment deleted successfully
+ Error in deleting comment
+ Just now
+
+ - %d second ago
+ - %d seconds ago
+
+
+ - %d minute ago
+ - %d minutes ago
+
+
+ - %d hour ago
+ - %d hours ago
+
+
+ - %d day ago
+ - %d days ago
+
+
+ - %d week ago
+ - %d weeks ago
+
+
+ - %d month ago
+ - %d months ago
+
+
+ - %d year ago
+ - %d years ago
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 83482af7f956..1e5767c77630 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1068,7 +1068,7 @@
Restore file
Restore
New version was created
- New comment…
+ Write a message…
Error commenting file
Error restoring file version!
General notifications