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 0cd2d5e30a31..7f60839e844e 100644 --- a/app/src/main/java/com/owncloud/android/services/OperationsService.java +++ b/app/src/main/java/com/owncloud/android/services/OperationsService.java @@ -48,6 +48,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; @@ -63,6 +64,7 @@ import com.owncloud.android.operations.UpdateShareInfoOperation; import com.owncloud.android.operations.UpdateSharePermissionsOperation; import com.owncloud.android.operations.UpdateShareViaLinkOperation; +import com.owncloud.android.operations.comments.GetCommentsRemoteOperation; import java.io.IOException; import java.util.Iterator; @@ -100,6 +102,7 @@ public class OperationsService extends Service { public static final String EXTRA_SHARE_ID = "SHARE_ID"; public static final String EXTRA_SHARE_NOTE = "SHARE_NOTE"; public static final String EXTRA_IN_BACKGROUND = "IN_BACKGROUND"; + 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"; @@ -120,6 +123,7 @@ public class OperationsService extends Service { public static final String ACTION_COPY_FILE = "COPY_FILE"; public static final String ACTION_CHECK_CURRENT_CREDENTIALS = "CHECK_CURRENT_CREDENTIALS"; public static final String ACTION_RESTORE_VERSION = "RESTORE_VERSION"; + public static final String ACTION_GET_COMMENTS = "GET_COMMENTS"; private ServiceHandler mOperationsHandler; private OperationsServiceBinder mOperationsBinder; @@ -735,6 +739,15 @@ private Pair newOperation(Intent operationIntent) { fileVersion.getFileName()); 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/ActivitiesActivity.java b/app/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java index 53aeb637f0cb..4f9a31574516 100644 --- a/app/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java @@ -20,6 +20,7 @@ import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.activities.model.RichObject; import com.owncloud.android.lib.resources.files.FileUtils; +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.DrawerActivity; @@ -102,7 +103,8 @@ private void setupContent() { this, clientFactory, false, - viewThemeUtils); + viewThemeUtils, + null); binding.list.setAdapter(adapter); LinearLayoutManager layoutManager = new LinearLayoutManager(this); @@ -161,6 +163,11 @@ public void onActivityClicked(RichObject richObject) { actionListener.openActivity(path, this); } + @Override + public void onCommentsOverflowMenuClicked(Comments comments) { + //nothing to be done here + } + @Override public void showActivities(List activities, NextcloudClient client, int lastGiven) { boolean clear = false; diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java index 1fa32f5e3309..45224bf27818 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -1613,7 +1613,8 @@ public void onBrowsedDownTo(OCFile directory) { */ @Override public void showDetails(OCFile file) { - showDetails(file, 0); + // NMC: use 1 as activeTab + showDetails(file, 1); } /** diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java index 3c6b724e553f..b5de146e7348 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java @@ -186,6 +186,6 @@ private FileDetailSharingFragment getShareFileFragment() { @Override public void onShareProcessClosed() { - finish(); + //nothing to do here } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ActivityAndVersionListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/ActivityAndVersionListAdapter.java index fb5fc712f992..9ad0bf082fbb 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ActivityAndVersionListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ActivityAndVersionListAdapter.java @@ -19,6 +19,7 @@ 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; @@ -42,14 +43,16 @@ public ActivityAndVersionListAdapter( ActivityListInterface activityListInterface, VersionListInterface.View versionListInterface, ClientFactory clientFactory, - ViewThemeUtils viewThemeUtils + ViewThemeUtils viewThemeUtils, + String userId ) { super(context, currentAccountProvider, activityListInterface, clientFactory, true, - viewThemeUtils); + viewThemeUtils, + userId); this.versionListInterface = versionListInterface; } @@ -65,12 +68,16 @@ public void setActivityAndVersionItems(List items, NextcloudClient newCl long o2Date; if (o1 instanceof Activity) { o1Date = ((Activity) o1).getDatetime().getTime(); + } else if (o1 instanceof Comments) { + o1Date = ((Comments) o1).getCreationDateTime().getTime(); } else { o1Date = ((FileVersion) o1).getModifiedTimestamp(); } if (o2 instanceof Activity) { o2Date = ((Activity) o2).getDatetime().getTime(); + } else if (o2 instanceof Comments) { + o2Date = ((Comments) o2).getCreationDateTime().getTime(); } else { o2Date = ((FileVersion) o2).getModifiedTimestamp(); } @@ -86,6 +93,9 @@ public void setActivityAndVersionItems(List items, NextcloudClient newCl if (item instanceof Activity) { Activity activity = (Activity) item; time = getHeaderDateString(context, activity.getDatetime().getTime()).toString(); + } else if (item instanceof Comments) { + Comments comments = (Comments) item; + time = getHeaderDateString(context, comments.getCreationDateTime().getTime()).toString(); } else { FileVersion version = (FileVersion) item; time = getHeaderDateString(context, version.getModifiedTimestamp()).toString(); @@ -138,6 +148,8 @@ public int getItemViewType(int position) { if (value instanceof Activity) { return ACTIVITY_TYPE; + } else if (value instanceof Comments) { + return COMMENT_TYPE; } else if (value instanceof FileVersion) { return VERSION_TYPE; } else { diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java index bec119241585..7322588bc64e 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java @@ -47,11 +47,13 @@ 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.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.interfaces.ActivityListInterface; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.MimeTypeUtil; @@ -66,6 +68,7 @@ import java.util.Locale; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; /** @@ -75,6 +78,7 @@ public class ActivityListAdapter extends RecyclerView.Adapter values; private boolean isDetailView; private ViewThemeUtils viewThemeUtils; + @Nullable + private final String userId; //it will be null if coming from activities public ActivityListAdapter( Context context, @@ -93,7 +99,8 @@ public ActivityListAdapter( ActivityListInterface activityListInterface, ClientFactory clientFactory, boolean isDetailView, - ViewThemeUtils viewThemeUtils) { + ViewThemeUtils viewThemeUtils, + @Nullable String userId) { this.values = new ArrayList<>(); this.context = context; this.currentAccountProvider = currentAccountProvider; @@ -102,6 +109,7 @@ public ActivityListAdapter( px = getThumbnailDimension(); this.isDetailView = isDetailView; this.viewThemeUtils = viewThemeUtils; + this.userId = userId; } public void setActivityItems(List activityItems, NextcloudClient client, boolean clear) { @@ -135,6 +143,10 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int return new ActivityViewHolder( ActivityListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false) ); + } else if (viewType == COMMENT_TYPE) { + return new CommentViewHolder( + CommentListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false) + ); } else { return new ActivityViewHeaderHolder( ActivityListItemHeaderBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false) @@ -216,6 +228,38 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi activityViewHolder.binding.list.removeAllViews(); activityViewHolder.binding.list.setVisibility(View.GONE); } + } else if (holder instanceof CommentViewHolder) { + final CommentViewHolder commentViewHolder = (CommentViewHolder) holder; + + Comments comments = (Comments) values.get(position); + + if (comments.getCreationDateTime() != null) { + String date = DisplayUtils.getRelativeDateTimeString(context, comments.getCreationDateTime().getTime()); + commentViewHolder.binding.datetime.setText(date); + commentViewHolder.binding.datetime.setVisibility(View.VISIBLE); + } else { + commentViewHolder.binding.datetime.setVisibility(View.GONE); + } + + if (!TextUtils.isEmpty(comments.getActorDisplayName())) { + commentViewHolder.binding.subject.setVisibility(View.VISIBLE); + commentViewHolder.binding.subject.setText(comments.getActorDisplayName()); + } else { + commentViewHolder.binding.subject.setVisibility(View.GONE); + } + + if (!TextUtils.isEmpty(comments.getMessage())) { + commentViewHolder.binding.message.setText(comments.getMessage()); + commentViewHolder.binding.message.setVisibility(View.VISIBLE); + } else { + commentViewHolder.binding.message.setVisibility(View.GONE); + } + + if (!TextUtils.isEmpty(comments.getActorId()) && userId != null && comments.getActorId().equals(userId)) { + commentViewHolder.binding.overflowMenu.setVisibility(View.VISIBLE); + commentViewHolder.binding.overflowMenu.setOnClickListener(v -> activityListInterface.onCommentsOverflowMenuClicked(comments)); + } + } else { ActivityViewHeaderHolder activityViewHeaderHolder = (ActivityViewHeaderHolder) holder; activityViewHeaderHolder.binding.header.setText((String) values.get(position)); @@ -343,6 +387,8 @@ private RichObject searchObjectByName(List richObjectList, String na public int getItemViewType(int position) { if (values.get(position) instanceof Activity) { return ACTIVITY_TYPE; + } else if (values.get(position) instanceof Comments) { + return COMMENT_TYPE; } else { return HEADER_TYPE; } @@ -372,7 +418,9 @@ private int getThumbnailDimension() { CharSequence getHeaderDateString(Context context, long modificationTimestamp) { if ((System.currentTimeMillis() - modificationTimestamp) < DateUtils.WEEK_IN_MILLIS) { return DisplayUtils.getRelativeDateTimeString(context, modificationTimestamp, DateUtils.DAY_IN_MILLIS, - DateUtils.WEEK_IN_MILLIS, 0); + DateUtils.WEEK_IN_MILLIS, 0, + //true to avoid creating wrong header date if date is 1sec future in case of comments + true); } else { return DateFormat.format(DateFormat.getBestDateTimePattern( Locale.getDefault(), "EEEE, MMMM d"), modificationTimestamp); @@ -420,6 +468,16 @@ protected class ActivityViewHolder extends RecyclerView.ViewHolder { } } + protected class CommentViewHolder extends RecyclerView.ViewHolder { + + CommentListItemBinding binding; + + CommentViewHolder(CommentListItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + protected class ActivityViewHeaderHolder extends RecyclerView.ViewHolder { ActivityListItemHeaderBinding binding; diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java index 3c20828d1950..528160031eb5 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java @@ -56,14 +56,15 @@ public ImageDetailFragment getImageDetailFragment() { @Override public Fragment createFragment(int position) { return switch (position) { + // NMC: Sharing will be 1st tab and comments will be 2nd tab default -> { - fileDetailActivitiesFragment = FileDetailActivitiesFragment.newInstance(file, user); - yield fileDetailActivitiesFragment; - } - case 1 -> { fileDetailSharingFragment = FileDetailSharingFragment.newInstance(file, user); yield fileDetailSharingFragment; } + case 1 -> { + fileDetailActivitiesFragment = FileDetailActivitiesFragment.newInstance(file, user); + yield fileDetailActivitiesFragment; + } case 2 -> { imageDetailFragment = ImageDetailFragment.newInstance(file, user); yield imageDetailFragment; 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 00254dfc15c7..753d68ab3a8e 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 @@ -255,6 +255,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 c45d2f854fe1..fa1f81739fb7 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 @@ -225,7 +225,8 @@ class SendShareDialog : BottomSheetDialogFragment(R.layout.send_share_fragment), 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/FileDetailActivitiesFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java index 68618877fa33..0f277c384eca 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java @@ -7,14 +7,19 @@ */ package com.owncloud.android.ui.fragment; +import android.accounts.AccountManager; +import android.app.Dialog; import android.content.ContentResolver; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Bundle; import android.text.Editable; +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 com.google.android.material.snackbar.Snackbar; import com.nextcloud.client.account.User; @@ -24,6 +29,7 @@ import com.nextcloud.common.NextcloudClient; import com.nextcloud.utils.extensions.BundleExtensionsKt; import com.nextcloud.utils.extensions.FileExtensionsKt; +import com.nmc.android.ui.CommentsActionsBottomSheetDialog; import com.owncloud.android.R; import com.owncloud.android.databinding.FileDetailsActivitiesFragmentBinding; import com.owncloud.android.datamodel.FileDataStorageManager; @@ -31,15 +37,18 @@ import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.lib.resources.activities.GetActivitiesRemoteOperation; import com.owncloud.android.lib.resources.activities.model.RichObject; import com.owncloud.android.lib.resources.comments.MarkCommentsAsReadRemoteOperation; -import com.owncloud.android.lib.resources.files.ReadFileVersionsRemoteOperation; import com.owncloud.android.lib.resources.files.model.FileVersion; import com.owncloud.android.lib.resources.status.OCCapability; 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.activity.ComponentsGetter; import com.owncloud.android.ui.adapter.ActivityAndVersionListAdapter; +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; @@ -50,7 +59,6 @@ import org.apache.commons.httpclient.HttpStatus; import org.greenrobot.eventbus.EventBus; -import java.util.ArrayList; import java.util.List; import javax.inject.Inject; @@ -58,17 +66,18 @@ import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; 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.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; public class FileDetailActivitiesFragment extends Fragment implements ActivityListInterface, DisplayUtils.AvatarGenerationListener, VersionListInterface.View, + CommentsActionsBottomSheetDialog.CommentsBottomSheetActions, Injectable { private static final String TAG = FileDetailActivitiesFragment.class.getSimpleName(); @@ -163,7 +172,16 @@ public void onError(int error) { binding.submitComment.setOnClickListener(v -> submitComment()); - viewThemeUtils.material.colorTextInputLayout(binding.commentInputFieldContainer); + binding.commentInputField.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + submitComment(); + return true; + } + return false; + } + }); DisplayUtils.setAvatar(user, this, @@ -217,19 +235,24 @@ private void setupView() { binding.emptyList.emptyListIcon.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_activity, null)); binding.emptyList.emptyListView.setVisibility(View.GONE); + AccountManager acctManager = AccountManager.get(getContext()); + String userId = acctManager.getUserData(user.toPlatformAccount(), + com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID); + adapter = new ActivityAndVersionListAdapter(getContext(), accountManager, this, this, clientFactory, - viewThemeUtils + viewThemeUtils, + userId ); binding.list.setAdapter(adapter); LinearLayoutManager layoutManager = new LinearLayoutManager(getContext()); binding.list.setLayoutManager(layoutManager); - binding.list.addOnScrollListener(new RecyclerView.OnScrollListener() { + /*binding.list.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { @@ -246,17 +269,82 @@ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { fetchAndSetData(lastGiven); } } - }); + });*/ } public void reload() { fetchAndSetData(-1); } + private void fetchAndSetData(int lastGiven) { + final FragmentActivity activity = getActivity(); + + if (activity == null) { + Log_OC.e(this, "Activity is null, aborting!"); + return; + } + + final User user = accountManager.getUser(); + + if (user.isAnonymous()) { + activity.runOnUiThread(() -> { + setEmptyContent(getString(R.string.common_error), getString(R.string.file_detail_comment_error)); + }); + return; + } + + Thread t = new Thread(() -> { + try { + ownCloudClient = clientFactory.create(user); + nextcloudClient = clientFactory.createNextcloudClient(user); + + isLoadingActivities = true; + + GetCommentsRemoteOperation getCommentsRemoteOperation = new GetCommentsRemoteOperation(file.getLocalId(), 0, 0); + + Log_OC.d(TAG, "BEFORE getCommentsRemoteOperation.execute"); + RemoteOperationResult result = getCommentsRemoteOperation.execute(ownCloudClient); + + + if (result.isSuccess() && result.getResultData() != null) { + List commentsList = (List) result.getResultData(); + + + activity.runOnUiThread(() -> { + if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { + populateList(commentsList, lastGiven == -1); + } + }); + } else { + Log_OC.d(TAG, result.getLogMessage()); + // show error + String logMessage = result.getLogMessage(); + if (result.getHttpCode() == HttpStatus.SC_NOT_MODIFIED) { + logMessage = getString(R.string.activities_no_results_message); + } + final String finalLogMessage = logMessage; + activity.runOnUiThread(() -> { + if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { + setErrorContent(finalLogMessage); + isLoadingActivities = false; + } + }); + } + + hideRefreshLayoutLoader(activity); + } catch (ClientFactory.CreationException e) { + 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 int; -1 to disable */ - private void fetchAndSetData(int lastGiven) { + /*private void fetchAndSetData(int lastGiven) { final FragmentActivity activity = getActivity(); if (activity == null) { @@ -272,7 +360,7 @@ private void fetchAndSetData(int lastGiven) { }); return; } - + if (!isLoadingActivities) { return; } @@ -354,7 +442,7 @@ private void fetchAndSetData(int lastGiven) { }); t.start(); - } + }*/ public void markCommentsAsRead() { new Thread(() -> { @@ -376,8 +464,8 @@ public void populateList(List activities, boolean clear) { if (adapter.getItemCount() == 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.setVisibility(View.VISIBLE); @@ -388,7 +476,8 @@ public void populateList(List activities, boolean clear) { } private void setEmptyContent(String headline, String message) { - setInfoContent(R.drawable.ic_activity, headline, message); + // NMC: no icon required for empty state + setInfoContent(0, headline, message); } @VisibleForTesting @@ -397,9 +486,16 @@ public void setErrorContent(String message) { } private void setInfoContent(@DrawableRes int icon, String headline, String message) { - binding.emptyList.emptyListIcon.setImageDrawable(ResourcesCompat.getDrawable(requireContext().getResources(), - icon, - null)); + // NMC: to handle no icon visibility + if (icon != 0) { + binding.emptyList.emptyListIcon.setImageDrawable(ResourcesCompat.getDrawable(requireContext().getResources(), + icon, + null)); + binding.emptyList.emptyListIcon.setVisibility(View.VISIBLE); + } else { + binding.emptyList.emptyListIcon.setVisibility(View.GONE); + } + binding.emptyList.emptyListViewHeadline.setText(headline); binding.emptyList.emptyListViewText.setText(message); @@ -408,7 +504,6 @@ private void setInfoContent(@DrawableRes int icon, String headline, String messa binding.emptyList.emptyListViewHeadline.setVisibility(View.VISIBLE); binding.emptyList.emptyListViewText.setVisibility(View.VISIBLE); - binding.emptyList.emptyListIcon.setVisibility(View.VISIBLE); binding.emptyList.emptyListView.setVisibility(View.VISIBLE); binding.swipeContainingEmpty.setVisibility(View.VISIBLE); } @@ -418,7 +513,6 @@ private void hideRefreshLayoutLoader(FragmentActivity activity) { if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { binding.swipeContainingList.setRefreshing(false); binding.swipeContainingEmpty.setRefreshing(false); - binding.emptyList.emptyListView.setVisibility(View.GONE); isLoadingActivities = false; } }); @@ -429,6 +523,11 @@ public void onActivityClicked(RichObject richObject) { // TODO implement activity click } + @Override + public void onCommentsOverflowMenuClicked(Comments comments) { + new CommentsActionsBottomSheetDialog(requireContext(), comments, this).show(); + } + @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); @@ -451,12 +550,32 @@ public void avatarGenerated(Drawable avatarDrawable, Object callContext) { public boolean shouldCallGeneratedCallback(String tag, Object callContext) { return false; } - + @VisibleForTesting public void disableLoadingActivities() { isLoadingActivities = false; } + @Override + public void onUpdateComment(@NonNull Comments comments) { + EditCommentDialogFragment dialog = EditCommentDialogFragment.newInstance(comments); + dialog.setOnEditCommentListener((comments1, message) -> { + new UpdateCommentTask(message, file.getLocalId(), comments1.getCommentId(), callback, ownCloudClient).execute(); + }); + dialog.show(requireActivity().getSupportFragmentManager(), EditCommentDialogFragment.EDIT_COMMENT_FRAGMENT_TAG); + } + + @Override + public void onDeleteComment(@NonNull Comments comments) { + AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()); + builder.setPositiveButton(R.string.common_yes, (dialogInterface, i) -> new DeleteCommentTask(file.getLocalId(), comments.getCommentId(), + callback, ownCloudClient).execute()) + .setNegativeButton(R.string.common_no, null) + .setMessage(R.string.delete_comment_dialog_message); + Dialog dialog = builder.create(); + dialog.show(); + } + private static class SubmitCommentTask extends AsyncTask { private final String message; @@ -495,4 +614,80 @@ protected void onPostExecute(Boolean success) { } } } + + private static class UpdateCommentTask extends AsyncTask { + + private final String message; + private final long fileId; + private final int commentId; + private final VersionListInterface.CommentCallback callback; + private final OwnCloudClient client; + + private UpdateCommentTask(String message, long fileId, int commentId, VersionListInterface.CommentCallback callback, + OwnCloudClient client) { + this.message = message; + this.fileId = fileId; + this.commentId = commentId; + this.callback = callback; + this.client = client; + } + + @Override + protected Boolean doInBackground(Void... voids) { + UpdateCommentRemoteOperation updateCommentRemoteOperation = new UpdateCommentRemoteOperation(fileId, commentId, message); + + RemoteOperationResult result = updateCommentRemoteOperation.execute(client); + + return result.isSuccess(); + } + + @Override + protected void onPostExecute(Boolean success) { + super.onPostExecute(success); + 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); + } + } + } + + private static class DeleteCommentTask extends AsyncTask { + + private final long fileId; + private final int commentId; + private final VersionListInterface.CommentCallback callback; + private final OwnCloudClient client; + + private DeleteCommentTask(long fileId, int commentId, VersionListInterface.CommentCallback callback, + OwnCloudClient client) { + this.fileId = fileId; + this.commentId = commentId; + this.callback = callback; + this.client = client; + } + + @Override + protected Boolean doInBackground(Void... voids) { + DeleteCommentRemoteOperation deleteCommentRemoteOperation = new DeleteCommentRemoteOperation(fileId, commentId); + + RemoteOperationResult result = deleteCommentRemoteOperation.execute(client); + + return result.isSuccess(); + } + + @Override + protected void onPostExecute(Boolean success) { + super.onPostExecute(success); + 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 a9fd7d134b1f..1691200658ad 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 @@ -183,6 +183,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(); } @@ -300,13 +305,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.ic_activity)); - - if (showSharingTab()) { - binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.share_dialog_title).setIcon(R.drawable.shared_via_users)); + // 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 (MimeTypeUtil.isImage(getFile())) { binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.filedetails_details).setIcon(R.drawable.image_32dp)); } @@ -480,11 +486,11 @@ public void onClick(View v) { setFileModificationTimestamp(getFile(), showDetailedTimestamp); } else if (id == R.id.folder_sync_button) { if (binding.folderSyncButton.isChecked()) { - getFile().setInternalFolderSyncTimestamp(0L); + getFile().setInternalFolderSyncTimestamp(0L); } else { getFile().setInternalFolderSyncTimestamp(-1L); } - + storageManager.saveFile(getFile()); } else { Log_OC.e(TAG, "Incorrect view clicked!"); @@ -569,11 +575,11 @@ public void updateFileDetails(boolean transferring, boolean refresh) { if (fabMain != null) { fabMain.hide(); } - + binding.syncBlock.setVisibility(file.isFolder() ? View.VISIBLE : View.GONE); - + if (file.isInternalFolderSync()) { - binding.folderSyncButton.setChecked(file.isInternalFolderSync()); + binding.folderSyncButton.setChecked(file.isInternalFolderSync()); } else { if (storageManager.isPartOfInternalTwoWaySync(file)) { binding.folderSyncButton.setChecked(true); diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 5854953c8d12..c60f34330cf9 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -176,6 +176,8 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, setupView(); + showHideSharingTitle(); + return binding.getRoot(); } @@ -255,6 +257,15 @@ private void checkShareViaUser() { } } + // 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 void showHideSharingTitle() { + if (requireActivity() instanceof FileDisplayActivity) { + // binding.sharingTitle.setVisibility(View.GONE); + } + } + private void disableSearchView(View view) { view.setEnabled(false); 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 fc806d4ab8cb..bb774264f003 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 @@ -494,6 +494,7 @@ class FileDetailsSharingProcessFragment : private fun removeCurrentFragment() { onEditShareListener.onShareProcessClosed() fileActivity?.supportFragmentManager?.beginTransaction()?.remove(this)?.commit() + requireActivity().supportFragmentManager.popBackStack() } private fun getReSharePermission(): Int { 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 7e4be1179a03..6fe7d4d86145 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 @@ -606,7 +606,8 @@ public void createRichWorkspace() { @Override public void onShareIconClick(OCFile file) { if (file.isFolder()) { - mContainerActivity.showDetails(file, 1); + // NMC: use 0 as activeTab + mContainerActivity.showDetails(file, 0); } else { throttler.run("shareIconClick", () -> { mContainerActivity.getFileOperationsHelper().sendShareFile(file); @@ -621,7 +622,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 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 6b765c2afd2d..c48049413c1d 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 @@ -768,6 +768,18 @@ public void updateShareInformation(OCShare share, int permissions, 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 750aa057b4d3..79205eb1e1a3 100644 --- a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java +++ b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java @@ -100,6 +100,7 @@ import java.util.Locale; import java.util.Map; import java.util.TimeZone; +import java.util.concurrent.TimeUnit; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -371,6 +372,54 @@ public static CharSequence getRelativeDateTimeString(Context c, return dateString.toString(); } + /** + * 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; + } + /** * Update the passed path removing the last "/" if it is not the root folder. * 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 38b9c9c4bb8a..25d4ede8f5e5 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_image.xml b/app/src/main/res/layout/grid_image.xml index 69e064ac87f4..a0f70fd9d3fa 100644 --- a/app/src/main/res/layout/grid_image.xml +++ b/app/src/main/res/layout/grid_image.xml @@ -104,7 +104,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:visibility="visible" /> diff --git a/app/src/main/res/layout/grid_item.xml b/app/src/main/res/layout/grid_item.xml index e7f2a6d8b661..16dc19e7eb51 100644 --- a/app/src/main/res/layout/grid_item.xml +++ b/app/src/main/res/layout/grid_item.xml @@ -106,7 +106,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:visibility="visible" /> 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 e9ec2516e131..37971dbc4ca7 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -574,7 +574,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 @@ -957,7 +957,7 @@ Kontakt nicht gefunden, Sie können jederzeit synchronisieren für eine Aktualisierung. Weiterleiten zum Web… Berechtigungen für das Öffnen der Suchergebnisse notwendig, ansonsten werden Sie zum Web weitergeleitet 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 8972d2bb6716..350abc5a9450 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -38,4 +38,70 @@ @android:color/white #101418 + + + #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 e0da603d4f8a..5e9e0a93d500 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -31,6 +31,7 @@ #757575 + #33E20074 #222222 #EEEEEE #BDBDBD @@ -78,4 +79,95 @@ #A5A5A5 #F7F9FF + + + #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 5026b2e41a42..ab85d534759e 100644 --- a/app/src/main/res/values/dims.xml +++ b/app/src/main/res/values/dims.xml @@ -98,7 +98,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 4d2893ca24d2..e145946bf871 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -892,7 +892,7 @@ Restore file Restore New version was created - New comment… + Write a message… Error commenting file Successfully restored file version. Error restoring file version!