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