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 @@ + + + + + + + + + + + + + + + + + + + +