From c629bdbb5949c135de39cf109ce3336d60faea89 Mon Sep 17 00:00:00 2001 From: A117870935 Date: Tue, 12 Sep 2023 18:09:27 +0530 Subject: [PATCH] Comment functionality customized as per NMC. --- .../ui/CommentsActionsBottomSheetDialog.kt | 51 ++++ .../android/operations/comments/Comments.kt | 63 +++++ .../DeleteCommentRemoteOperation.java | 82 +++++++ .../comments/GetCommentsRemoteOperation.java | 217 ++++++++++++++++ .../UpdateCommentRemoteOperation.java | 93 +++++++ .../android/services/OperationsService.java | 13 + .../adapter/ActivityAndVersionListAdapter.kt | 8 +- .../activities/adapter/ActivityListAdapter.kt | 58 ++++- .../ui/activity/FileDisplayActivity.kt | 3 +- .../android/ui/activity/ShareActivity.kt | 2 +- .../ui/adapter/FileDetailTabAdapter.kt | 5 +- .../android/ui/adapter/OCFileListDelegate.kt | 6 + .../ui/dialog/EditCommentDialogFragment.java | 181 ++++++++++++++ .../android/ui/dialog/SendShareDialog.kt | 3 +- .../android/ui/fragment/ActivitiesFragment.kt | 3 + .../fragment/FileDetailActivitiesFragment.kt | 231 ++++++++++++++++-- .../ui/fragment/FileDetailFragment.java | 19 +- .../ui/fragment/FileDetailSharingFragment.kt | 13 + .../FileDetailsSharingProcessFragment.kt | 1 + .../ui/fragment/OCFileListFragment.java | 10 +- .../ui/helpers/FileOperationsHelper.java | 12 + .../ui/interfaces/ActivityListInterface.java | 3 + .../owncloud/android/utils/DisplayUtils.java | 49 ++++ .../activity_list_item_header_background.xml | 9 + .../main/res/drawable/ic_delete_comment.xml | 12 + app/src/main/res/drawable/ic_pencil_edit.xml | 12 + .../res/drawable/share_search_background.xml | 17 ++ .../res/layout/activity_list_item_header.xml | 29 ++- app/src/main/res/layout/comment_list_item.xml | 86 +++++++ .../res/layout/comment_list_item_shimmer.xml | 46 ++++ ...comments_actions_bottom_sheet_fragment.xml | 81 ++++++ .../file_details_activities_fragment.xml | 55 +++-- app/src/main/res/layout/grid_item.xml | 2 +- .../res/values-de/nmc_comments_strings.xml | 51 ++++ app/src/main/res/values-de/strings.xml | 4 +- app/src/main/res/values-night/colors.xml | 66 +++++ app/src/main/res/values/colors.xml | 92 +++++++ app/src/main/res/values/dimens.xml | 32 +++ app/src/main/res/values/dims.xml | 2 +- .../main/res/values/nmc_comments_strings.xml | 51 ++++ app/src/main/res/values/strings.xml | 2 +- 41 files changed, 1685 insertions(+), 90 deletions(-) create mode 100644 app/src/main/java/com/nmc/android/ui/CommentsActionsBottomSheetDialog.kt create mode 100644 app/src/main/java/com/owncloud/android/operations/comments/Comments.kt create mode 100644 app/src/main/java/com/owncloud/android/operations/comments/DeleteCommentRemoteOperation.java create mode 100644 app/src/main/java/com/owncloud/android/operations/comments/GetCommentsRemoteOperation.java create mode 100644 app/src/main/java/com/owncloud/android/operations/comments/UpdateCommentRemoteOperation.java create mode 100644 app/src/main/java/com/owncloud/android/ui/dialog/EditCommentDialogFragment.java create mode 100644 app/src/main/res/drawable/activity_list_item_header_background.xml create mode 100644 app/src/main/res/drawable/ic_delete_comment.xml create mode 100644 app/src/main/res/drawable/ic_pencil_edit.xml create mode 100644 app/src/main/res/drawable/share_search_background.xml create mode 100644 app/src/main/res/layout/comment_list_item.xml create mode 100644 app/src/main/res/layout/comment_list_item_shimmer.xml create mode 100644 app/src/main/res/layout/comments_actions_bottom_sheet_fragment.xml create mode 100644 app/src/main/res/values-de/nmc_comments_strings.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/nmc_comments_strings.xml 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