Add comments

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
tobiasKaminsky 2018-06-01 13:10:33 +02:00
parent 1ef3d299e6
commit 5e50b6cf66
No known key found for this signature in database
GPG key ID: 0E00D4D47D0C5AF7
10 changed files with 235 additions and 152 deletions

View file

@ -0,0 +1,112 @@
/*
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* Copyright (C) 2018 Tobias Kaminsky
* Copyright (C) 2018 Nextcloud GmbH.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.operations;
import android.util.Log;
import com.google.gson.GsonBuilder;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.operations.common.SyncOperation;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Restore a {@link com.owncloud.android.lib.resources.files.FileVersion}.
*/
public class CommentFileOperation extends SyncOperation {
private static final String TAG = CommentFileOperation.class.getSimpleName();
private static final int POST_READ_TIMEOUT = 30000;
private static final int POST_CONNECTION_TIMEOUT = 5000;
private static final String ACTOR_ID = "actorId";
private static final String ACTOR_TYPE = "actorType";
private static final String ACTOR_TYPE_VALUE = "users";
private static final String VERB = "verb";
private static final String VERB_VALUE = "comment";
private static final String MESSAGE = "message";
private String message;
private String fileId;
private String userId;
/**
* Constructor
*
* @param message Comment to store
* @param userId userId to access correct dav endpoint
*/
public CommentFileOperation(String message, String fileId, String userId) {
this.message = message;
this.fileId = fileId;
this.userId = userId;
}
/**
* Performs the operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result;
try {
String url = client.getNewWebdavUri(false) + "/comments/files/" + fileId;
PostMethod postMethod = new PostMethod(url);
postMethod.addRequestHeader("Content-type", "application/json");
Map<String, String> values = new HashMap<>();
values.put(ACTOR_ID, userId);
values.put(ACTOR_TYPE, ACTOR_TYPE_VALUE);
values.put(VERB, VERB_VALUE);
values.put(MESSAGE, message);
String json = new GsonBuilder().create().toJson(values, Map.class);
postMethod.setRequestEntity(new StringRequestEntity(json));
int status = client.executeMethod(postMethod, POST_READ_TIMEOUT, POST_CONNECTION_TIMEOUT);
result = new RemoteOperationResult(isSuccess(status), postMethod);
client.exhaustResponse(postMethod.getResponseBodyAsStream());
} catch (IOException e) {
result = new RemoteOperationResult(e);
Log.e(TAG, "Post comment to file with id " + fileId + " failed: " + result.getLogMessage(), e);
}
return result;
}
private boolean isSuccess(int status) {
return status == HttpStatus.SC_CREATED;
}
}

View file

@ -36,7 +36,6 @@ import android.widget.ProgressBar;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
import com.owncloud.android.utils.ThemeUtils;
/**

View file

@ -50,7 +50,6 @@ import com.owncloud.android.ui.dialog.ExpirationDatePickerDialogFragment;
import com.owncloud.android.ui.fragment.util.SharingMenuHelper;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.ThemeUtils;
import com.owncloud.android.utils.DisplayUtils;
import java.security.NoSuchAlgorithmException;
import java.util.List;
@ -378,23 +377,6 @@ public class UserListAdapter extends RecyclerView.Adapter<UserListAdapter.UserVi
}
}
@Override
public void avatarGenerated(Drawable avatarDrawable, Object callContext) {
if (callContext instanceof ImageView) {
ImageView iv = (ImageView) callContext;
iv.setImageDrawable(avatarDrawable);
}
}
@Override
public boolean shouldCallGeneratedCallback(String tag, Object callContext) {
if (callContext instanceof ImageView) {
ImageView iv = (ImageView) callContext;
return String.valueOf(iv.getTag()).equals(tag);
}
return false;
}
public interface ShareeListAdapterListener {
/**
* unshare with given sharee {@link OCShare}.

View file

@ -30,6 +30,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TextInputEditText;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.widget.SwipeRefreshLayout;
@ -38,6 +39,7 @@ import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
@ -59,6 +61,7 @@ import com.owncloud.android.lib.resources.files.FileVersion;
import com.owncloud.android.lib.resources.files.ReadFileVersionsOperation;
import com.owncloud.android.lib.resources.status.OCCapability;
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
import com.owncloud.android.operations.CommentFileOperation;
import com.owncloud.android.ui.activity.ComponentsGetter;
import com.owncloud.android.ui.activity.FileActivity;
import com.owncloud.android.ui.adapter.ActivityAndVersionListAdapter;
@ -115,6 +118,12 @@ public class FileDetailActivitiesFragment extends Fragment implements ActivityLi
@BindView(android.R.id.list)
public RecyclerView recyclerView;
@BindView(R.id.submitComment)
public Button submitComment;
@BindView(R.id.commentInputField)
public TextInputEditText commentInput;
@BindString(R.string.activities_no_results_headline)
public String noResultsHeadline;
@ -165,10 +174,30 @@ public class FileDetailActivitiesFragment extends Fragment implements ActivityLi
userId = accountManager.getUserData(account,
com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID);
submitComment.setOnClickListener((l) -> submitComment(new VersionListInterface.CommentCallback() {
@Override
public void onSuccess() {
commentInput.getText().clear();
fetchAndSetData(null);
}
@Override
public void onError(int error) {
Snackbar.make(recyclerView, error, Snackbar.LENGTH_LONG).show();
}
}));
return view;
}
private void submitComment(VersionListInterface.CommentCallback callback) {
String message = commentInput.getText().toString();
new RestoreFileVersionTask.SubmitCommentTask(message, userId, file.getLocalId(), callback, ownCloudClient)
.execute();
}
private void onRefreshListLayout(SwipeRefreshLayout refreshLayout) {
setLoadingMessage();
if (refreshLayout != null && refreshLayout.isRefreshing()) {
@ -379,6 +408,17 @@ public class FileDetailActivitiesFragment extends Fragment implements ActivityLi
// TODO implement activity click
}
@Override
public void onSuccess(String message) {
Snackbar.make(recyclerView, message, Snackbar.LENGTH_LONG).show();
fetchAndSetData(null);
}
@Override
public void onSuccess() {
fetchAndSetData(null);
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
@ -391,4 +431,43 @@ public class FileDetailActivitiesFragment extends Fragment implements ActivityLi
public void onRestoreClicked(FileVersion fileVersion) {
operationsHelper.restoreFileVersion(fileVersion, userId);
}
private static class SubmitCommentTask extends AsyncTask<Void, Void, Boolean> {
private String message;
private String userId;
private String fileId;
private VersionListInterface.CommentCallback callback;
private OwnCloudClient client;
private SubmitCommentTask(String message, String userId, String fileId,
VersionListInterface.CommentCallback callback, OwnCloudClient client) {
this.message = message;
this.userId = userId;
this.fileId = fileId;
this.callback = callback;
this.client = client;
}
@Override
protected Boolean doInBackground(Void... voids) {
CommentFileOperation commentFileOperation = new CommentFileOperation(message, fileId, userId);
RemoteOperationResult result = commentFileOperation.execute(client);
return result.isSuccess();
}
@Override
protected void onPostExecute(Boolean success) {
super.onPostExecute(success);
if (success) {
callback.onSuccess();
} else {
callback.onError(R.string.error_comment_file);
}
}
}
}

View file

@ -28,7 +28,6 @@ import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
@ -39,7 +38,6 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.TextView;
@ -68,7 +66,6 @@ import java.lang.ref.WeakReference;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Optional;
import butterknife.Unbinder;
/**
@ -126,60 +123,6 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
private ToolbarActivity activity;
private int activeTab;
@Nullable @BindView(R.id.fdProgressBlock)
View downloadProgressContainer;
@Nullable @BindView(R.id.fdCancelBtn)
ImageButton cancelButton;
@Nullable @BindView(R.id.fdProgressBar)
ProgressBar progressBar;
@Nullable @BindView(R.id.fdProgressText)
TextView progressText;
@Nullable @BindView(R.id.fdFilename)
TextView fileName;
@Nullable @BindView(R.id.fdSize)
TextView fileSize;
@Nullable @BindView(R.id.fdModified)
TextView fileModifiedTimestamp;
@Nullable @BindView(R.id.fdFavorite)
ImageView favoriteIcon;
@Nullable @BindView(R.id.overflow_menu)
ImageView overflowMenu;
@Nullable @BindView(R.id.tab_layout)
TabLayout tabLayout;
@Nullable @BindView(R.id.pager)
ViewPager viewPager;
@Nullable @BindView(R.id.empty_list_view_text)
protected TextView emptyContentMessage;
@Nullable @BindView(R.id.empty_list_view_headline)
protected TextView emptyContentHeadline;
@Nullable @BindView(R.id.empty_list_icon)
protected ImageView emptyContentIcon;
@Nullable @BindView(R.id.empty_list_progress)
protected ProgressBar emptyProgressBar;
private int layout;
private View view;
private boolean previewLoaded;
private Account account;
private Unbinder unbinder;
public ProgressListener progressListener;
private ToolbarActivity activity;
/**
* Public factory method to create new FileDetailFragment instances.
*
@ -306,72 +249,10 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
cancelButton.setOnClickListener(this);
favoriteIcon.setOnClickListener(this);
overflowMenu.setOnClickListener(this);
cancelButton.setOnClickListener(this);
favoriteIcon.setOnClickListener(this);
overflowMenu.setOnClickListener(this);
// TODO use whenever we switch to use glide for preview images
/*
if (MimeTypeUtil.isImage(getFile())) {
setHeaderImage();
}
*/
updateFileDetails(false, false);
} else {
emptyContentMessage.setText(R.string.filedetails_select_file);
emptyContentMessage.setVisibility(View.VISIBLE);
emptyContentHeadline.setText(R.string.common_error);
emptyContentIcon.setImageDrawable(getContext().getResources().getDrawable(R.drawable.ic_alert_octagon));
emptyContentIcon.setVisibility(View.VISIBLE);
emptyProgressBar.setVisibility(View.GONE);
}
}
// TODO use whenever we switch to use glide for preview images
/*
private void setHeaderImage() {
if (mContainerActivity.getStorageManager().getCapability(account.name)
.getServerBackground() != null && previewImage != null) {
String background = mContainerActivity.getStorageManager().getCapability(account.name).getServerBackground();
int primaryColor = ThemeUtils.primaryColor(account, getContext());
if (URLUtil.isValidUrl(background)) {
// background image
SimpleTarget target = new SimpleTarget<Drawable>() {
@Override
public void onResourceReady(Drawable resource, GlideAnimation glideAnimation) {
Drawable[] drawables = {new ColorDrawable(primaryColor), resource};
LayerDrawable layerDrawable = new LayerDrawable(drawables);
previewImage.setImageDrawable(layerDrawable);
previewImage.setVisibility(View.VISIBLE);
((ToolbarActivity) getActivity()).getSupportActionBar().setTitle(null);
((ToolbarActivity) getActivity()).getSupportActionBar().setBackgroundDrawable(null);
toolbarProgressBar.setVisibility(View.GONE);
previewLoaded = true;
}
@Override
public void onLoadFailed(Exception e, Drawable errorDrawable) {
previewImage.setVisibility(View.GONE);
toolbarProgressBar.setVisibility(View.VISIBLE);
}
};
Glide.with(this)
.load(background)
.centerCrop()
.placeholder(R.drawable.background)
.error(R.drawable.background)
.crossFade()
.into(target);
} else {
// hide image
previewImage.setVisibility(View.GONE);
toolbarProgressBar.setVisibility(View.VISIBLE);
}
}
}
*/
public void onOverflowIconClicked(View view) {
PopupMenu popup = new PopupMenu(getActivity(), view);

View file

@ -60,7 +60,7 @@ public class FileFragment extends Fragment {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
setFile((OCFile) bundle.getParcelable(EXTRA_FILE));
setFile(bundle.getParcelable(EXTRA_FILE));
}
/**

View file

@ -28,4 +28,10 @@ public interface VersionListInterface {
interface View {
void onRestoreClicked(FileVersion fileVersion);
}
interface CommentCallback {
void onSuccess();
void onError(int error);
}
}

View file

@ -34,7 +34,9 @@ import com.owncloud.android.lib.common.utils.Log_OC;
import org.apache.commons.codec.binary.Hex;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;
/**

View file

@ -18,36 +18,56 @@
License along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<android.support.design.widget.TextInputEditText
android:id="@+id/commentInputField"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/new_comment"/>
<Button
android:id="@+id/submitComment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="→"/>
</LinearLayout>
<android.support.v4.widget.SwipeRefreshLayout
android:footerDividersEnabled="false"
android:id="@+id/swipe_containing_list"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:footerDividersEnabled="false"
android:visibility="visible">
<android.support.v7.widget.RecyclerView
android:clipToPadding="false"
android:id="@android:id/list"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical"
android:visibility="visible" />
android:visibility="visible"/>
</android.support.v4.widget.SwipeRefreshLayout>
<android.support.v4.widget.SwipeRefreshLayout
android:footerDividersEnabled="false"
android:id="@+id/swipe_containing_empty"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:footerDividersEnabled="false"
android:visibility="visible">
<include layout="@layout/empty_list" />
<include layout="@layout/empty_list"/>
</android.support.v4.widget.SwipeRefreshLayout>
</FrameLayout>
</LinearLayout>

View file

@ -799,6 +799,8 @@
<string name="whats_new_device_credentials_content">Use anything like a pattern, password, pin or your fingerprint to keep your data safe.</string>
<string name="restore">Restore file</string>
<string name="new_version_was_created">New version was created</string>
<string name="new_comment">New comment…</string>
<string name="error_comment_file">Error commenting file</string>
<string name="file_version_restored_successfully">Successfully restored file version.</string>
<string name="file_version_restored_error">Error restoring file version!</string>
<string name="outdated_server">The server has reached end of life, please upgrade!</string>