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