diff --git a/docs-android/app/src/main/java/com/sismics/docs/MainApplication.java b/docs-android/app/src/main/java/com/sismics/docs/MainApplication.java
index bb2b0936..17f3fe9d 100644
--- a/docs-android/app/src/main/java/com/sismics/docs/MainApplication.java
+++ b/docs-android/app/src/main/java/com/sismics/docs/MainApplication.java
@@ -20,10 +20,8 @@ public class MainApplication extends Application {
JSONObject json = PreferenceUtil.getCachedJson(getApplicationContext(), PreferenceUtil.PREF_CACHED_USER_INFO_JSON);
ApplicationContext.getInstance().setUserInfo(getApplicationContext(), json);
- // TODO Tags caching
// TODO Fullscreen preview
// TODO Caching preferences
- // TODO Edit sharing
// TODO Documents adding/editing
// TODO Files adding/deleting
diff --git a/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentActivity.java b/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentActivity.java
index 3f11336b..9650bdd9 100644
--- a/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentActivity.java
+++ b/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentActivity.java
@@ -6,6 +6,7 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
+import android.support.v4.app.DialogFragment;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBarActivity;
import android.text.format.DateFormat;
@@ -19,6 +20,7 @@ import android.widget.TextView;
import com.sismics.docs.R;
import com.sismics.docs.adapter.FilePagerAdapter;
import com.sismics.docs.event.DocumentFullscreenEvent;
+import com.sismics.docs.fragment.DocShareFragment;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.resource.FileResource;
@@ -182,6 +184,11 @@ public class DocumentActivity extends ActionBarActivity {
downloadZip();
return true;
+ case R.id.share:
+ DialogFragment dialog = DocShareFragment.newInstance(document.optString("id"));
+ dialog.show(getSupportFragmentManager(), "DocShareFragment");
+ return true;
+
case android.R.id.home:
finish();
return true;
diff --git a/docs-android/app/src/main/java/com/sismics/docs/adapter/ShareListAdapter.java b/docs-android/app/src/main/java/com/sismics/docs/adapter/ShareListAdapter.java
new file mode 100644
index 00000000..d0fffc76
--- /dev/null
+++ b/docs-android/app/src/main/java/com/sismics/docs/adapter/ShareListAdapter.java
@@ -0,0 +1,88 @@
+package com.sismics.docs.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+import com.sismics.docs.R;
+import com.sismics.docs.event.ShareDeleteEvent;
+import com.sismics.docs.event.ShareSendEvent;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import de.greenrobot.event.EventBus;
+
+/**
+ * Share list adapter.
+ *
+ * @author bgamard.
+ */
+public class ShareListAdapter extends BaseAdapter {
+ /**
+ * Shares.
+ */
+ private JSONArray shares;
+
+ /**
+ * Share list adapter.
+ *
+ * @param shares Shares
+ */
+ public ShareListAdapter(JSONArray shares) {
+ this.shares = shares;
+ }
+
+ @Override
+ public int getCount() {
+ return shares.length();
+ }
+
+ @Override
+ public JSONObject getItem(int position) {
+ return shares.optJSONObject(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return getItem(position).optString("id").hashCode();
+ }
+
+ @Override
+ public View getView(int position, View view, final ViewGroup parent) {
+ if (view == null) {
+ LayoutInflater vi = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ view = vi.inflate(R.layout.share_list_item, parent, false);
+ }
+
+ // Fill the view
+ final JSONObject share = getItem(position);
+ String name = share.optString("name");
+ TextView shareTextView = (TextView) view.findViewById(R.id.shareTextView);
+ shareTextView.setText(name.isEmpty() ? parent.getContext().getString(R.string.share_default_name) : name);
+
+ // Delete a share
+ ImageButton shareDeleteButton = (ImageButton) view.findViewById(R.id.shareDeleteButton);
+ shareDeleteButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ EventBus.getDefault().post(new ShareDeleteEvent(share.optString("id")));
+ }
+ });
+
+ // Send the link
+ ImageButton shareSendButton = (ImageButton) view.findViewById(R.id.shareSendButton);
+ shareSendButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ EventBus.getDefault().post(new ShareSendEvent(share));
+ }
+ });
+
+ return view;
+ }
+}
diff --git a/docs-android/app/src/main/java/com/sismics/docs/event/ShareDeleteEvent.java b/docs-android/app/src/main/java/com/sismics/docs/event/ShareDeleteEvent.java
new file mode 100644
index 00000000..1097e4fc
--- /dev/null
+++ b/docs-android/app/src/main/java/com/sismics/docs/event/ShareDeleteEvent.java
@@ -0,0 +1,26 @@
+package com.sismics.docs.event;
+
+/**
+ * Share delete event.
+ *
+ * @author bgamard.
+ */
+public class ShareDeleteEvent {
+ /**
+ * Share ID
+ */
+ private String id;
+
+ /**
+ * Create a share delete event.
+ *
+ * @param id Share ID
+ */
+ public ShareDeleteEvent(String id) {
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+}
diff --git a/docs-android/app/src/main/java/com/sismics/docs/event/ShareSendEvent.java b/docs-android/app/src/main/java/com/sismics/docs/event/ShareSendEvent.java
new file mode 100644
index 00000000..8e48e1ca
--- /dev/null
+++ b/docs-android/app/src/main/java/com/sismics/docs/event/ShareSendEvent.java
@@ -0,0 +1,28 @@
+package com.sismics.docs.event;
+
+import org.json.JSONObject;
+
+/**
+ * Share send event.
+ *
+ * @author bgamard.
+ */
+public class ShareSendEvent {
+ /**
+ * Share data.
+ */
+ private JSONObject share;
+
+ /**
+ * Create a share send event.
+ *
+ * @param share Share data
+ */
+ public ShareSendEvent(JSONObject share) {
+ this.share = share;
+ }
+
+ public JSONObject getShare() {
+ return share;
+ }
+}
diff --git a/docs-android/app/src/main/java/com/sismics/docs/fragment/DocShareFragment.java b/docs-android/app/src/main/java/com/sismics/docs/fragment/DocShareFragment.java
new file mode 100644
index 00000000..d910a1b6
--- /dev/null
+++ b/docs-android/app/src/main/java/com/sismics/docs/fragment/DocShareFragment.java
@@ -0,0 +1,186 @@
+package com.sismics.docs.fragment;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.sismics.docs.R;
+import com.sismics.docs.adapter.ShareListAdapter;
+import com.sismics.docs.event.ShareDeleteEvent;
+import com.sismics.docs.event.ShareSendEvent;
+import com.sismics.docs.listener.JsonHttpResponseHandler;
+import com.sismics.docs.resource.DocumentResource;
+import com.sismics.docs.resource.ShareResource;
+import com.sismics.docs.util.PreferenceUtil;
+
+import org.apache.http.Header;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import de.greenrobot.event.EventBus;
+
+/**
+ * Document sharing dialog fragment.
+ *
+ * @author bgamard.
+ */
+public class DocShareFragment extends DialogFragment {
+ /**
+ * Document data.
+ */
+ private JSONObject document;
+
+ /**
+ * Document sharing dialog fragment
+ * @param id Document ID
+ */
+ public static DocShareFragment newInstance(String id) {
+ DocShareFragment fragment = new DocShareFragment();
+ Bundle args = new Bundle();
+ args.putString("id", id);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+ // Setup the view
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+ View view = inflater.inflate(R.layout.document_share_dialog, null);
+ final Button shareAddButton = (Button) view.findViewById(R.id.shareAddButton);
+ final EditText shareNameEditText = (EditText) view.findViewById(R.id.shareNameEditText);
+
+ // Add a share
+ shareAddButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ shareNameEditText.setEnabled(false);
+ shareAddButton.setEnabled(false);
+
+ ShareResource.add(getActivity(), getArguments().getString("id"), shareNameEditText.getText().toString(),
+ new JsonHttpResponseHandler() {
+ @Override
+ public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
+ shareNameEditText.setText("");
+ loadShares(getDialog().getWindow().getDecorView());
+ }
+
+ @Override
+ public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
+ Toast.makeText(getActivity(), R.string.error_adding_share, Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onFinish() {
+ shareNameEditText.setEnabled(true);
+ shareAddButton.setEnabled(true);
+ }
+ });
+ }
+ });
+
+ // Get the shares
+ loadShares(view);
+
+ // Build the dialog
+ builder.setView(view)
+ .setNegativeButton(R.string.close, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ getDialog().cancel();
+ }
+ });
+ return builder.create();
+ }
+
+ /**
+ * Load the shares.
+ *
+ * @param view View
+ */
+ private void loadShares(View view) {
+ if (isDetached()) return;
+
+ final ListView shareListView = (ListView) view.findViewById(R.id.shareListView);
+ final TextView shareEmptyView = (TextView) view.findViewById(R.id.shareEmptyView);
+ final ProgressBar shareProgressBar = (ProgressBar) view.findViewById(R.id.shareProgressBar);
+
+ shareListView.setEmptyView(shareProgressBar);
+ DocumentResource.get(getActivity(), getArguments().getString("id"), new JsonHttpResponseHandler() {
+ @Override
+ public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
+ document = response;
+ JSONArray shares = response.optJSONArray("shares");
+ shareProgressBar.setVisibility(View.GONE);
+ shareListView.setEmptyView(shareEmptyView);
+ shareListView.setAdapter(new ShareListAdapter(shares));
+ }
+
+ @Override
+ public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
+ getDialog().cancel();
+ Toast.makeText(getActivity(), R.string.error_loading_shares, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ public void onEvent(ShareDeleteEvent event) {
+ ShareResource.delete(getActivity(), event.getId(), new JsonHttpResponseHandler() {
+ @Override
+ public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
+ loadShares(getDialog().getWindow().getDecorView());
+ }
+
+ @Override
+ public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
+ Toast.makeText(getActivity(), R.string.error_deleting_share, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ public void onEvent(ShareSendEvent event) {
+ if (document == null) return;
+
+ // Build the share link
+ String serverUrl = PreferenceUtil.getServerUrl(getActivity());
+ String link = serverUrl + "/share.html#/share/" + document.optString("id") + "/" + event.getShare().optString("id");
+
+ // Build the intent
+ Context context = getActivity();
+ Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_SEND);
+ intent.putExtra(Intent.EXTRA_SUBJECT, document.optString("title"));
+ intent.putExtra(Intent.EXTRA_TEXT, link);
+ intent.setType("text/plain");
+
+ // Open the target chooser
+ context.startActivity(Intent.createChooser(intent, context.getText(R.string.send_share_to)));
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ EventBus.getDefault().register(this);
+ }
+
+ @Override
+ public void onDestroy() {
+ EventBus.getDefault().unregister(this);
+ super.onDestroy();
+ }
+}
diff --git a/docs-android/app/src/main/java/com/sismics/docs/resource/DocumentResource.java b/docs-android/app/src/main/java/com/sismics/docs/resource/DocumentResource.java
index 526010f8..502976ab 100644
--- a/docs-android/app/src/main/java/com/sismics/docs/resource/DocumentResource.java
+++ b/docs-android/app/src/main/java/com/sismics/docs/resource/DocumentResource.java
@@ -30,4 +30,17 @@ public class DocumentResource extends BaseResource {
params.put("search", query);
client.get(getApiUrl(context) + "/document/list", params, responseHandler);
}
+
+ /**
+ * GET /document/id.
+ *
+ * @param context Context
+ * @param id ID
+ * @param responseHandler Callback
+ */
+ public static void get(Context context, String id, JsonHttpResponseHandler responseHandler) {
+ init(context);
+
+ client.get(getApiUrl(context) + "/document/" + id, responseHandler);
+ }
}
diff --git a/docs-android/app/src/main/java/com/sismics/docs/resource/ShareResource.java b/docs-android/app/src/main/java/com/sismics/docs/resource/ShareResource.java
new file mode 100644
index 00000000..833d993e
--- /dev/null
+++ b/docs-android/app/src/main/java/com/sismics/docs/resource/ShareResource.java
@@ -0,0 +1,44 @@
+package com.sismics.docs.resource;
+
+import android.content.Context;
+
+import com.loopj.android.http.RequestParams;
+import com.sismics.docs.listener.JsonHttpResponseHandler;
+
+
+/**
+ * Access to /tag API.
+ *
+ * @author bgamard
+ */
+public class ShareResource extends BaseResource {
+ /**
+ * PUT /share.
+ *
+ * @param context Context
+ * @param documentId Document ID
+ * @param name Name
+ * @param responseHandler Callback
+ */
+ public static void add(Context context, String documentId, String name, JsonHttpResponseHandler responseHandler) {
+ init(context);
+
+ RequestParams params = new RequestParams();
+ params.put("id", documentId);
+ params.put("name", name);
+ client.put(getApiUrl(context) + "/share", params, responseHandler);
+ }
+
+ /**
+ * DELETE /share.
+ *
+ * @param context Context
+ * @param id ID
+ * @param responseHandler Callback
+ */
+ public static void delete(Context context, String id, JsonHttpResponseHandler responseHandler) {
+ init(context);
+
+ client.delete(getApiUrl(context) + "/share/" + id, responseHandler);
+ }
+}
diff --git a/docs-android/app/src/main/res/drawable-xhdpi/ic_delete_grey600_24dp.png b/docs-android/app/src/main/res/drawable-xhdpi/ic_delete_grey600_24dp.png
new file mode 100644
index 00000000..c6bb43e8
Binary files /dev/null and b/docs-android/app/src/main/res/drawable-xhdpi/ic_delete_grey600_24dp.png differ
diff --git a/docs-android/app/src/main/res/drawable-xhdpi/ic_link_grey600_24dp.png b/docs-android/app/src/main/res/drawable-xhdpi/ic_link_grey600_24dp.png
new file mode 100644
index 00000000..b0afb76a
Binary files /dev/null and b/docs-android/app/src/main/res/drawable-xhdpi/ic_link_grey600_24dp.png differ
diff --git a/docs-android/app/src/main/res/drawable-xhdpi/ic_share_grey600_24dp.png b/docs-android/app/src/main/res/drawable-xhdpi/ic_share_grey600_24dp.png
new file mode 100644
index 00000000..88a0edd6
Binary files /dev/null and b/docs-android/app/src/main/res/drawable-xhdpi/ic_share_grey600_24dp.png differ
diff --git a/docs-android/app/src/main/res/drawable-xxhdpi/ic_delete_grey600_24dp.png b/docs-android/app/src/main/res/drawable-xxhdpi/ic_delete_grey600_24dp.png
new file mode 100644
index 00000000..4886ab1e
Binary files /dev/null and b/docs-android/app/src/main/res/drawable-xxhdpi/ic_delete_grey600_24dp.png differ
diff --git a/docs-android/app/src/main/res/drawable-xxhdpi/ic_link_grey600_24dp.png b/docs-android/app/src/main/res/drawable-xxhdpi/ic_link_grey600_24dp.png
new file mode 100644
index 00000000..65832a65
Binary files /dev/null and b/docs-android/app/src/main/res/drawable-xxhdpi/ic_link_grey600_24dp.png differ
diff --git a/docs-android/app/src/main/res/drawable-xxhdpi/ic_share_grey600_24dp.png b/docs-android/app/src/main/res/drawable-xxhdpi/ic_share_grey600_24dp.png
new file mode 100644
index 00000000..89136d7c
Binary files /dev/null and b/docs-android/app/src/main/res/drawable-xxhdpi/ic_share_grey600_24dp.png differ
diff --git a/docs-android/app/src/main/res/layout/document_share_dialog.xml b/docs-android/app/src/main/res/layout/document_share_dialog.xml
new file mode 100644
index 00000000..6405694b
--- /dev/null
+++ b/docs-android/app/src/main/res/layout/document_share_dialog.xml
@@ -0,0 +1,69 @@
+
+