Implement 'Get link' button
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / fragment / ShareFileFragment.java
1 /**
2 * ownCloud Android client application
3 *
4 * @author masensio
5 * @author David A. Velasco
6 * Copyright (C) 2015 ownCloud Inc.
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2,
10 * as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22 package com.owncloud.android.ui.fragment;
23
24 import android.accounts.Account;
25 import android.app.Activity;
26 import android.content.Intent;
27 import android.graphics.Bitmap;
28 import android.os.Bundle;
29 import android.support.v4.app.DialogFragment;
30 import android.support.v4.app.Fragment;
31 import android.support.v7.widget.AppCompatButton;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.widget.Button;
36 import android.widget.CompoundButton;
37 import android.widget.ImageView;
38 import android.widget.ListView;
39 import android.widget.Switch;
40 import android.widget.TextView;
41 import android.widget.Toast;
42
43 import com.owncloud.android.R;
44 import com.owncloud.android.authentication.AccountUtils;
45 import com.owncloud.android.datamodel.OCFile;
46 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
47 import com.owncloud.android.files.FileOperationsHelper;
48 import com.owncloud.android.lib.common.utils.Log_OC;
49 import com.owncloud.android.lib.resources.shares.OCShare;
50 import com.owncloud.android.lib.resources.shares.ShareType;
51 import com.owncloud.android.ui.activity.FileActivity;
52 import com.owncloud.android.ui.adapter.ShareUserListAdapter;
53 import com.owncloud.android.ui.dialog.ExpirationDatePickerDialogFragment;
54 import com.owncloud.android.utils.DisplayUtils;
55 import com.owncloud.android.utils.MimetypeIconUtil;
56
57 import java.text.SimpleDateFormat;
58
59 import java.util.ArrayList;
60 import java.util.Date;
61
62 /**
63 * Fragment for Sharing a file with sharees (users or groups) or creating
64 * a public link.
65 *
66 * A simple {@link Fragment} subclass.
67 *
68 * Activities that contain this fragment must implement the
69 * {@link ShareFileFragment.OnShareFragmentInteractionListener} interface
70 * to handle interaction events.
71 *
72 * Use the {@link ShareFileFragment#newInstance} factory method to
73 * create an instance of this fragment.
74 */
75 public class ShareFileFragment extends Fragment
76 implements ShareUserListAdapter.ShareUserAdapterListener{
77
78 private static final String TAG = ShareFileFragment.class.getSimpleName();
79
80 /** The fragment initialization parameters */
81 private static final String ARG_FILE = "FILE";
82 private static final String ARG_ACCOUNT = "ACCOUNT";
83
84 // /** Tag for dialog */
85 // private static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG";
86
87 /** File to share, received as a parameter in construction time */
88 private OCFile mFile;
89
90 /** OC account holding the file to share, received as a parameter in construction time */
91 private Account mAccount;
92
93 /** Reference to parent listener */
94 private OnShareFragmentInteractionListener mListener;
95
96 /** List of private shares bound to the file */
97 private ArrayList<OCShare> mPrivateShares;
98
99 /** Adapter to show private shares */
100 private ShareUserListAdapter mUserGroupsAdapter = null;
101
102 /** Public share bound to the file */
103 private OCShare mPublicShare;
104
105 /** Listener for changes on switch to share / unshare publicly */
106 private CompoundButton.OnCheckedChangeListener mOnShareViaLinkSwitchCheckedChangeListener;
107
108 /**
109 * Listener for user actions to set, update or clear password on public link
110 */
111 private OnPasswordInteractionListener mOnPasswordInteractionListener = null;
112
113 /**
114 * Listener for user actions to set, update or clear expiration date on public link
115 */
116 private OnExpirationDateInteractionListener mOnExpirationDateInteractionListener = null;
117
118 /**
119 * Public factory method to create new ShareFileFragment instances.
120 *
121 * @param fileToShare An {@link OCFile} to show in the fragment
122 * @param account An ownCloud account
123 * @return A new instance of fragment ShareFileFragment.
124 */
125 public static ShareFileFragment newInstance(OCFile fileToShare, Account account) {
126 ShareFileFragment fragment = new ShareFileFragment();
127 Bundle args = new Bundle();
128 args.putParcelable(ARG_FILE, fileToShare);
129 args.putParcelable(ARG_ACCOUNT, account);
130 fragment.setArguments(args);
131 return fragment;
132 }
133
134 public ShareFileFragment() {
135 // Required empty public constructor
136 }
137
138 /**
139 * {@inheritDoc}
140 */
141 @Override
142 public void onCreate(Bundle savedInstanceState) {
143 super.onCreate(savedInstanceState);
144 if (getArguments() != null) {
145 mFile = getArguments().getParcelable(ARG_FILE);
146 mAccount = getArguments().getParcelable(ARG_ACCOUNT);
147 }
148 }
149
150
151 /**
152 * {@inheritDoc}
153 */
154 @Override
155 public View onCreateView(LayoutInflater inflater, ViewGroup container,
156 Bundle savedInstanceState) {
157 // Inflate the layout for this fragment
158 View view = inflater.inflate(R.layout.share_file_layout, container, false);
159
160 // Setup layout
161 // Image
162 ImageView icon = (ImageView) view.findViewById(R.id.shareFileIcon);
163 icon.setImageResource(MimetypeIconUtil.getFileTypeIconId(mFile.getMimetype(),
164 mFile.getFileName()));
165 if (mFile.isImage()) {
166 String remoteId = String.valueOf(mFile.getRemoteId());
167 Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(remoteId);
168 if (thumbnail != null) {
169 icon.setImageBitmap(thumbnail);
170 }
171 }
172 // Name
173 TextView filename = (TextView) view.findViewById(R.id.shareFileName);
174 filename.setText(mFile.getFileName());
175 // Size
176 TextView size = (TextView) view.findViewById(R.id.shareFileSize);
177 if (mFile.isFolder()) {
178 size.setVisibility(View.GONE);
179 } else {
180 size.setText(DisplayUtils.bytesToHumanReadable(mFile.getFileLength()));
181 }
182
183 // Add User Button
184 Button addUserGroupButton = (Button)
185 view.findViewById(R.id.addUserButton);
186 addUserGroupButton.setOnClickListener(new View.OnClickListener() {
187 @Override
188 public void onClick(View view) {
189 boolean shareWithUsersEnable = AccountUtils.hasSearchUsersSupport(mAccount);
190 if (shareWithUsersEnable) {
191 // Show Search Fragment
192 mListener.showSearchUsersAndGroups();
193 } else {
194 String message = getString(R.string.share_sharee_unavailable);
195 Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
196 }
197 }
198 });
199
200 // Switch to create public share
201 mOnShareViaLinkSwitchCheckedChangeListener = new CompoundButton.OnCheckedChangeListener() {
202 @Override
203 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
204 if (!isResumed()) {
205 // very important, setCheched(...) is called automatically during
206 // Fragment recreation on device rotations
207 return;
208 }
209 if (isChecked) {
210 ((FileActivity) getActivity()).getFileOperationsHelper().
211 shareFileViaLink(mFile);
212
213 } else {
214 ((FileActivity) getActivity()).getFileOperationsHelper().
215 unshareFileViaLink(mFile);
216 }
217 }
218 };
219 Switch shareViaLinkSwitch = (Switch) view.findViewById(R.id.shareViaLinkSectionSwitch);
220 shareViaLinkSwitch.setOnCheckedChangeListener(mOnShareViaLinkSwitchCheckedChangeListener);
221
222 // Set listener for user actions on expiration date
223 initExpirationListener(view);
224
225 // Set listener for user actions on password
226 initPasswordListener(view);
227
228 return view;
229 }
230
231
232 /**
233 * Binds listener for user actions that start any update on a expiration date
234 * for the public link to the views receiving the user events.
235 *
236 * @param shareView Root view in the fragment.
237 */
238 private void initExpirationListener(View shareView) {
239 mOnExpirationDateInteractionListener = new OnExpirationDateInteractionListener();
240
241 ((Switch) shareView.findViewById(R.id.shareViaLinkExpirationSwitch)).
242 setOnCheckedChangeListener(mOnExpirationDateInteractionListener);
243
244 shareView.findViewById(R.id.shareViaLinkExpirationLabel).
245 setOnClickListener(mOnExpirationDateInteractionListener);
246
247 shareView.findViewById(R.id.shareViaLinkExpirationValue).
248 setOnClickListener(mOnExpirationDateInteractionListener);
249 }
250
251 /**
252 * Listener for user actions that start any update on the expiration date for the public link.
253 */
254 private class OnExpirationDateInteractionListener
255 implements CompoundButton.OnCheckedChangeListener, View.OnClickListener {
256
257 /**
258 * Called by R.id.shareViaLinkExpirationSwitch to set or clear the expiration date.
259 *
260 * @param switchView {@link Switch} toggled by the user, R.id.shareViaLinkExpirationSwitch
261 * @param isChecked New switch state.
262 */
263 @Override
264 public void onCheckedChanged(CompoundButton switchView, boolean isChecked) {
265 if (!isResumed()) {
266 // very important, setCheched(...) is called automatically during
267 // Fragment recreation on device rotations
268 return;
269 }
270 if (isChecked) {
271 ExpirationDatePickerDialogFragment dialog =
272 ExpirationDatePickerDialogFragment.newInstance(mFile, -1);
273 dialog.show(
274 getActivity().getSupportFragmentManager(),
275 ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG
276 );
277
278 } else {
279 ((FileActivity) getActivity()).getFileOperationsHelper().
280 setExpirationDateToShareViaLink(mFile, -1);
281 }
282
283 // undo the toggle to grant the view will be correct if the dialog is cancelled
284 switchView.setOnCheckedChangeListener(null);
285 switchView.toggle();
286 switchView.setOnCheckedChangeListener(mOnExpirationDateInteractionListener);
287 }
288
289 /**
290 * Called by R.id.shareViaLinkExpirationLabel or R.id.shareViaLinkExpirationValue
291 * to change the current expiration date.
292 *
293 * @param expirationView Label or value view touched by the user.
294 */
295 @Override
296 public void onClick(View expirationView) {
297 if (mPublicShare != null && mPublicShare.getExpirationDate() > 0) {
298 long chosenDateInMillis = -1;
299 if (mPublicShare != null) {
300 chosenDateInMillis = mPublicShare.getExpirationDate();
301 }
302 ExpirationDatePickerDialogFragment dialog =
303 ExpirationDatePickerDialogFragment.newInstance(
304 mFile,
305 chosenDateInMillis
306 );
307 dialog.show(
308 getActivity().getSupportFragmentManager(),
309 ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG
310 );
311 }
312 }
313 }
314
315
316 /**
317 * Binds listener for user actions that start any update on a password for the public link
318 * to the views receiving the user events.
319 *
320 * @param shareView Root view in the fragment.
321 */
322 private void initPasswordListener(View shareView) {
323 mOnPasswordInteractionListener = new OnPasswordInteractionListener();
324
325 ((Switch) shareView.findViewById(R.id.shareViaLinkPasswordSwitch)).
326 setOnCheckedChangeListener(mOnPasswordInteractionListener);
327
328 shareView.findViewById(R.id.shareViaLinkPasswordLabel).
329 setOnClickListener(mOnPasswordInteractionListener);
330
331 shareView.findViewById(R.id.shareViaLinkPasswordValue).
332 setOnClickListener(mOnPasswordInteractionListener);
333 }
334
335
336 /**
337 * Listener for user actions that start any update on a password for the public link.
338 */
339 private class OnPasswordInteractionListener
340 implements CompoundButton.OnCheckedChangeListener, View.OnClickListener {
341
342 /**
343 * Called by R.id.shareViaLinkPasswordSwitch to set or clear the password.
344 *
345 * @param switchView {@link Switch} toggled by the user, R.id.shareViaLinkPasswordSwitch
346 * @param isChecked New switch state.
347 */
348 @Override
349 public void onCheckedChanged(CompoundButton switchView, boolean isChecked) {
350 if (!isResumed()) {
351 // very important, setCheched(...) is called automatically during
352 // Fragment recreation on device rotations
353 return;
354 }
355 if (isChecked) {
356 ((FileActivity) getActivity()).getFileOperationsHelper().
357 requestPasswordForShareViaLink(mFile);
358 } else {
359 ((FileActivity) getActivity()).getFileOperationsHelper().
360 setPasswordToShareViaLink(mFile, ""); // "" clears
361 }
362
363 // undo the toggle to grant the view will be correct if the dialog is cancelled
364 switchView.setOnCheckedChangeListener(null);
365 switchView.toggle();
366 switchView.setOnCheckedChangeListener(mOnPasswordInteractionListener);
367 }
368
369 /**
370 * Called by R.id.shareViaLinkPasswordLabel or R.id.shareViaLinkPasswordValue
371 * to change the current password.
372 *
373 * @param passwordView Label or value view touched by the user.
374 */
375 @Override
376 public void onClick(View passwordView) {
377 if (mPublicShare != null && mPublicShare.isPasswordProtected()) {
378 ((FileActivity) getActivity()).getFileOperationsHelper().
379 requestPasswordForShareViaLink(mFile);
380 }
381 }
382 }
383
384
385 @Override
386 public void onActivityCreated(Bundle savedInstanceState) {
387 super.onActivityCreated(savedInstanceState);
388
389 // Load data into the list of private shares
390 refreshUsersOrGroupsListFromDB();
391
392 // Load data of public share, if exists
393 refreshPublicShareFromDB();
394 }
395
396 @Override
397 public void onAttach(Activity activity) {
398 super.onAttach(activity);
399 try {
400 mListener = (OnShareFragmentInteractionListener) activity;
401 } catch (ClassCastException e) {
402 throw new ClassCastException(activity.toString()
403 + " must implement OnShareFragmentInteractionListener");
404 }
405 }
406
407 @Override
408 public void onDetach() {
409 super.onDetach();
410 mListener = null;
411 }
412
413 /**
414 * Get users and groups from the DB to fill in the "share with" list.
415 *
416 * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
417 * instance ready to use. If not ready, does nothing.
418 */
419 public void refreshUsersOrGroupsListFromDB (){
420 if (((FileActivity) mListener).getStorageManager() != null) {
421 // Get Users and Groups
422 mPrivateShares = ((FileActivity) mListener).getStorageManager().getSharesWithForAFile(
423 mFile.getRemotePath(),
424 mAccount.name
425 );
426
427 // Update list of users/groups
428 updateListOfUserGroups();
429 }
430 }
431
432 private void updateListOfUserGroups() {
433 // Update list of users/groups
434 // TODO Refactoring: create a new {@link ShareUserListAdapter} instance with every call should not be needed
435 mUserGroupsAdapter = new ShareUserListAdapter(
436 getActivity(),
437 R.layout.share_user_item,
438 mPrivateShares,
439 this
440 );
441
442 // Show data
443 TextView noShares = (TextView) getView().findViewById(R.id.shareNoUsers);
444 ListView usersList = (ListView) getView().findViewById(R.id.shareUsersList);
445
446 if (mPrivateShares.size() > 0) {
447 noShares.setVisibility(View.GONE);
448 usersList.setVisibility(View.VISIBLE);
449 usersList.setAdapter(mUserGroupsAdapter);
450
451 } else {
452 noShares.setVisibility(View.VISIBLE);
453 usersList.setVisibility(View.GONE);
454 }
455 }
456
457 @Override
458 public void unshareButtonPressed(OCShare share) {
459 // Unshare
460 mListener.unshareWith(share);
461 Log_OC.d(TAG, "Unshare - " + share.getSharedWithDisplayName());
462 }
463
464
465
466 /**
467 * Get public link from the DB to fill in the "Share link" section in the UI.
468 *
469 * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
470 * instance ready to use. If not ready, does nothing.
471 */
472 public void refreshPublicShareFromDB() {
473 if (((FileActivity) mListener).getStorageManager() != null) {
474 // Get public share
475 mPublicShare = ((FileActivity) mListener).getStorageManager().getFirstShareByPathAndType(
476 mFile.getRemotePath(),
477 ShareType.PUBLIC_LINK,
478 ""
479 );
480
481 // Update public share section
482 updatePublicShareSection();
483 }
484 }
485
486 /**
487 * Updates in the UI the section about public share with the information in the current
488 * public share bound to mFile, if any
489 */
490 private void updatePublicShareSection() {
491 if (mPublicShare != null && ShareType.PUBLIC_LINK.equals(mPublicShare.getShareType())) {
492 /// public share bound -> expand section
493 Switch shareViaLinkSwitch = getShareViaLinkSwitch();
494 if (!shareViaLinkSwitch.isChecked()) {
495 // set null listener before setChecked() to prevent infinite loop of calls
496 shareViaLinkSwitch.setOnCheckedChangeListener(null);
497 shareViaLinkSwitch.setChecked(true);
498 shareViaLinkSwitch.setOnCheckedChangeListener(
499 mOnShareViaLinkSwitchCheckedChangeListener
500 );
501 }
502 getExpirationDateSection().setVisibility(View.VISIBLE);
503 getPasswordSection().setVisibility(View.VISIBLE);
504 // GetLink button
505 AppCompatButton getLinkButton = getGetLinkButton();
506 getLinkButton.setVisibility(View.VISIBLE);
507 getLinkButton.setOnClickListener(new View.OnClickListener() {
508 @Override
509 public void onClick(View v) {
510 //GetLink from the server and show ShareLinkToDialog
511 ((FileActivity) getActivity()).getFileOperationsHelper().
512 getFileWithLink(mFile);
513
514 }
515 });
516
517 /// update state of expiration date switch and message depending on expiration date
518 /// update state of expiration date switch and message depending on expiration date
519 Switch expirationDateSwitch = getExpirationDateSwitch();
520 // set null listener before setChecked() to prevent infinite loop of calls
521 expirationDateSwitch.setOnCheckedChangeListener(null);
522 long expirationDate = mPublicShare.getExpirationDate();
523 if (expirationDate > 0) {
524 if (!expirationDateSwitch.isChecked()) {
525 expirationDateSwitch.toggle();
526 }
527 String formattedDate =
528 SimpleDateFormat.getDateInstance().format(
529 new Date(expirationDate)
530 );
531 getExpirationDateValue().setText(formattedDate);
532 } else {
533 if (expirationDateSwitch.isChecked()) {
534 expirationDateSwitch.toggle();
535 }
536 getExpirationDateValue().setText(R.string.empty);
537 }
538 // recover listener
539 expirationDateSwitch.setOnCheckedChangeListener(
540 mOnExpirationDateInteractionListener
541 );
542
543 /// update state of password switch and message depending on password protection
544 Switch passwordSwitch = getPasswordSwitch();
545 // set null listener before setChecked() to prevent infinite loop of calls
546 passwordSwitch.setOnCheckedChangeListener(null);
547 if (mPublicShare.isPasswordProtected()) {
548 if (!passwordSwitch.isChecked()) {
549 passwordSwitch.toggle();
550 }
551 getPasswordValue().setVisibility(View.VISIBLE);
552 } else {
553 if (passwordSwitch.isChecked()) {
554 passwordSwitch.toggle();
555 }
556 getPasswordValue().setVisibility(View.INVISIBLE);
557 }
558 // recover listener
559 passwordSwitch.setOnCheckedChangeListener(
560 mOnPasswordInteractionListener
561 );
562
563
564 } else {
565 /// no public share -> collapse section
566 Switch shareViaLinkSwitch = getShareViaLinkSwitch();
567 if (shareViaLinkSwitch.isChecked()) {
568 shareViaLinkSwitch.setOnCheckedChangeListener(null);
569 getShareViaLinkSwitch().setChecked(false);
570 shareViaLinkSwitch.setOnCheckedChangeListener(
571 mOnShareViaLinkSwitchCheckedChangeListener
572 );
573 }
574 getExpirationDateSection().setVisibility(View.GONE);
575 getPasswordSection().setVisibility(View.GONE);
576 getGetLinkButton().setVisibility(View.GONE);
577 }
578 }
579
580
581 /// BEWARE: next methods will failed with NullPointerException if called before onCreateView() finishes
582
583 private Switch getShareViaLinkSwitch() {
584 return (Switch) getView().findViewById(R.id.shareViaLinkSectionSwitch);
585 }
586
587 private View getExpirationDateSection() {
588 return getView().findViewById(R.id.shareViaLinkExpirationSection);
589 }
590
591 private Switch getExpirationDateSwitch() {
592 return (Switch) getView().findViewById(R.id.shareViaLinkExpirationSwitch);
593 }
594
595 private TextView getExpirationDateValue() {
596 return (TextView) getView().findViewById(R.id.shareViaLinkExpirationValue);
597 }
598
599 private View getPasswordSection() {
600 return getView().findViewById(R.id.shareViaLinkPasswordSection);
601 }
602
603 private Switch getPasswordSwitch() {
604 return (Switch) getView().findViewById(R.id.shareViaLinkPasswordSwitch);
605 }
606
607 private TextView getPasswordValue() {
608 return (TextView) getView().findViewById(R.id.shareViaLinkPasswordValue);
609 }
610
611 private AppCompatButton getGetLinkButton() {
612 return (AppCompatButton) getView().findViewById(R.id.shareViewLinkGetLinkButton);
613 }
614
615 /**
616 * This interface must be implemented by activities that contain this
617 * fragment to allow an interaction in this fragment to be communicated
618 * to the activity and potentially other fragments contained in that
619 * activity.
620 * <p/>
621 * See the Android Training lesson <a href=
622 * "http://developer.android.com/training/basics/fragments/communicating.html"
623 * >Communicating with Other Fragments</a> for more information.
624 */
625 public interface OnShareFragmentInteractionListener {
626 void showSearchUsersAndGroups();
627 void refreshUsersOrGroupsListFromServer();
628 void unshareWith(OCShare share);
629 }
630
631 }