d9a9b44a3c93bd5738dd29c10fb52ca177604723
[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, -1);
266 dialog.show(
267 getActivity().getSupportFragmentManager(),
268 ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG
269 );
270
271 } else {
272 ((FileActivity) getActivity()).getFileOperationsHelper().
273 setExpirationDateToShareViaLink(mFile, -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 long chosenDateInMillis = -1;
292 if (mPublicShare != null) {
293 chosenDateInMillis = mPublicShare.getExpirationDate();
294 }
295 ExpirationDatePickerDialogFragment dialog =
296 ExpirationDatePickerDialogFragment.newInstance(
297 mFile,
298 chosenDateInMillis
299 );
300 dialog.show(
301 getActivity().getSupportFragmentManager(),
302 ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG
303 );
304 }
305 }
306 }
307
308
309 /**
310 * Binds listener for user actions that start any update on a password for the public link
311 * to the views receiving the user events.
312 *
313 * @param shareView Root view in the fragment.
314 */
315 private void initPasswordListener(View shareView) {
316 mOnPasswordInteractionListener = new OnPasswordInteractionListener();
317
318 ((Switch) shareView.findViewById(R.id.shareViaLinkPasswordSwitch)).
319 setOnCheckedChangeListener(mOnPasswordInteractionListener);
320
321 shareView.findViewById(R.id.shareViaLinkPasswordLabel).
322 setOnClickListener(mOnPasswordInteractionListener);
323
324 shareView.findViewById(R.id.shareViaLinkPasswordValue).
325 setOnClickListener(mOnPasswordInteractionListener);
326 }
327
328
329 /**
330 * Listener for user actions that start any update on a password for the public link.
331 */
332 private class OnPasswordInteractionListener
333 implements CompoundButton.OnCheckedChangeListener, View.OnClickListener {
334
335 /**
336 * Called by R.id.shareViaLinkPasswordSwitch to set or clear the password.
337 *
338 * @param switchView {@link Switch} toggled by the user, R.id.shareViaLinkPasswordSwitch
339 * @param isChecked New switch state.
340 */
341 @Override
342 public void onCheckedChanged(CompoundButton switchView, boolean isChecked) {
343 if (!isResumed()) {
344 // very important, setCheched(...) is called automatically during
345 // Fragment recreation on device rotations
346 return;
347 }
348 if (isChecked) {
349 ((FileActivity) getActivity()).getFileOperationsHelper().
350 requestPasswordForShareViaLink(mFile);
351 } else {
352 ((FileActivity) getActivity()).getFileOperationsHelper().
353 setPasswordToShareViaLink(mFile, ""); // "" clears
354 }
355
356 // undo the toggle to grant the view will be correct if the dialog is cancelled
357 switchView.setOnCheckedChangeListener(null);
358 switchView.toggle();
359 switchView.setOnCheckedChangeListener(mOnPasswordInteractionListener);
360 }
361
362 /**
363 * Called by R.id.shareViaLinkPasswordLabel or R.id.shareViaLinkPasswordValue
364 * to change the current password.
365 *
366 * @param passwordView Label or value view touched by the user.
367 */
368 @Override
369 public void onClick(View passwordView) {
370 if (mPublicShare != null && mPublicShare.isPasswordProtected()) {
371 ((FileActivity) getActivity()).getFileOperationsHelper().
372 requestPasswordForShareViaLink(mFile);
373 }
374 }
375 }
376
377
378 @Override
379 public void onActivityCreated(Bundle savedInstanceState) {
380 super.onActivityCreated(savedInstanceState);
381
382 // Load data into the list of private shares
383 refreshUsersOrGroupsListFromDB();
384
385 // Load data of public share, if exists
386 refreshPublicShareFromDB();
387 }
388
389 @Override
390 public void onAttach(Activity activity) {
391 super.onAttach(activity);
392 try {
393 mListener = (OnShareFragmentInteractionListener) activity;
394 } catch (ClassCastException e) {
395 throw new ClassCastException(activity.toString()
396 + " must implement OnShareFragmentInteractionListener");
397 }
398 }
399
400 @Override
401 public void onDetach() {
402 super.onDetach();
403 mListener = null;
404 }
405
406 /**
407 * Get users and groups from the DB to fill in the "share with" list.
408 *
409 * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
410 * instance ready to use. If not ready, does nothing.
411 */
412 public void refreshUsersOrGroupsListFromDB (){
413 if (((FileActivity) mListener).getStorageManager() != null) {
414 // Get Users and Groups
415 mPrivateShares = ((FileActivity) mListener).getStorageManager().getSharesWithForAFile(
416 mFile.getRemotePath(),
417 mAccount.name
418 );
419
420 // Update list of users/groups
421 updateListOfUserGroups();
422 }
423 }
424
425 private void updateListOfUserGroups() {
426 // Update list of users/groups
427 // TODO Refactoring: create a new {@link ShareUserListAdapter} instance with every call should not be needed
428 mUserGroupsAdapter = new ShareUserListAdapter(
429 getActivity(),
430 R.layout.share_user_item,
431 mPrivateShares,
432 this
433 );
434
435 // Show data
436 TextView noShares = (TextView) getView().findViewById(R.id.shareNoUsers);
437 ListView usersList = (ListView) getView().findViewById(R.id.shareUsersList);
438
439 if (mPrivateShares.size() > 0) {
440 noShares.setVisibility(View.GONE);
441 usersList.setVisibility(View.VISIBLE);
442 usersList.setAdapter(mUserGroupsAdapter);
443
444 } else {
445 noShares.setVisibility(View.VISIBLE);
446 usersList.setVisibility(View.GONE);
447 }
448 }
449
450 @Override
451 public void unshareButtonPressed(OCShare share) {
452 // Unshare
453 mListener.unshareWith(share);
454 Log_OC.d(TAG, "Unshare - " + share.getSharedWithDisplayName());
455 }
456
457
458
459 /**
460 * Get public link from the DB to fill in the "Share link" section in the UI.
461 *
462 * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
463 * instance ready to use. If not ready, does nothing.
464 */
465 public void refreshPublicShareFromDB() {
466 if (((FileActivity) mListener).getStorageManager() != null) {
467 // Get public share
468 mPublicShare = ((FileActivity) mListener).getStorageManager().getFirstShareByPathAndType(
469 mFile.getRemotePath(),
470 ShareType.PUBLIC_LINK,
471 ""
472 );
473
474 // Update public share section
475 updatePublicShareSection();
476 }
477 }
478
479 /**
480 * Updates in the UI the section about public share with the information in the current
481 * public share bound to mFile, if any
482 */
483 private void updatePublicShareSection() {
484 if (mPublicShare != null && ShareType.PUBLIC_LINK.equals(mPublicShare.getShareType())) {
485 /// public share bound -> expand section
486 Switch shareViaLinkSwitch = getShareViaLinkSwitch();
487 if (!shareViaLinkSwitch.isChecked()) {
488 // set null listener before setChecked() to prevent infinite loop of calls
489 shareViaLinkSwitch.setOnCheckedChangeListener(null);
490 shareViaLinkSwitch.setChecked(true);
491 shareViaLinkSwitch.setOnCheckedChangeListener(
492 mOnShareViaLinkSwitchCheckedChangeListener
493 );
494 }
495 getExpirationDateSection().setVisibility(View.VISIBLE);
496 getPasswordSection().setVisibility(View.VISIBLE);
497 getGetLinkButton().setVisibility(View.VISIBLE);
498
499 /// update state of expiration date switch and message depending on expiration date
500 /// update state of expiration date switch and message depending on expiration date
501 Switch expirationDateSwitch = getExpirationDateSwitch();
502 // set null listener before setChecked() to prevent infinite loop of calls
503 expirationDateSwitch.setOnCheckedChangeListener(null);
504 long expirationDate = mPublicShare.getExpirationDate();
505 if (expirationDate > 0) {
506 if (!expirationDateSwitch.isChecked()) {
507 expirationDateSwitch.toggle();
508 }
509 String formattedDate =
510 SimpleDateFormat.getDateInstance().format(
511 new Date(expirationDate)
512 );
513 getExpirationDateValue().setText(formattedDate);
514 } else {
515 if (expirationDateSwitch.isChecked()) {
516 expirationDateSwitch.toggle();
517 }
518 getExpirationDateValue().setText(R.string.empty);
519 }
520 // recover listener
521 expirationDateSwitch.setOnCheckedChangeListener(
522 mOnExpirationDateInteractionListener
523 );
524
525 /// update state of password switch and message depending on password protection
526 Switch passwordSwitch = getPasswordSwitch();
527 // set null listener before setChecked() to prevent infinite loop of calls
528 passwordSwitch.setOnCheckedChangeListener(null);
529 if (mPublicShare.isPasswordProtected()) {
530 if (!passwordSwitch.isChecked()) {
531 passwordSwitch.toggle();
532 }
533 getPasswordValue().setVisibility(View.VISIBLE);
534 } else {
535 if (passwordSwitch.isChecked()) {
536 passwordSwitch.toggle();
537 }
538 getPasswordValue().setVisibility(View.INVISIBLE);
539 }
540 // recover listener
541 passwordSwitch.setOnCheckedChangeListener(
542 mOnPasswordInteractionListener
543 );
544
545
546 } else {
547 /// no public share -> collapse section
548 Switch shareViaLinkSwitch = getShareViaLinkSwitch();
549 if (shareViaLinkSwitch.isChecked()) {
550 shareViaLinkSwitch.setOnCheckedChangeListener(null);
551 getShareViaLinkSwitch().setChecked(false);
552 shareViaLinkSwitch.setOnCheckedChangeListener(
553 mOnShareViaLinkSwitchCheckedChangeListener
554 );
555 }
556 getExpirationDateSection().setVisibility(View.GONE);
557 getPasswordSection().setVisibility(View.GONE);
558 getGetLinkButton().setVisibility(View.GONE);
559 }
560 }
561
562
563 /// BEWARE: next methods will failed with NullPointerException if called before onCreateView() finishes
564
565 private Switch getShareViaLinkSwitch() {
566 return (Switch) getView().findViewById(R.id.shareViaLinkSectionSwitch);
567 }
568
569 private View getExpirationDateSection() {
570 return getView().findViewById(R.id.shareViaLinkExpirationSection);
571 }
572
573 private Switch getExpirationDateSwitch() {
574 return (Switch) getView().findViewById(R.id.shareViaLinkExpirationSwitch);
575 }
576
577 private TextView getExpirationDateValue() {
578 return (TextView) getView().findViewById(R.id.shareViaLinkExpirationValue);
579 }
580
581 private View getPasswordSection() {
582 return getView().findViewById(R.id.shareViaLinkPasswordSection);
583 }
584
585 private Switch getPasswordSwitch() {
586 return (Switch) getView().findViewById(R.id.shareViaLinkPasswordSwitch);
587 }
588
589 private TextView getPasswordValue() {
590 return (TextView) getView().findViewById(R.id.shareViaLinkPasswordValue);
591 }
592
593 private AppCompatButton getGetLinkButton() {
594 return (AppCompatButton) getView().findViewById(R.id.shareViewLinkGetLinkButton);
595 }
596
597
598 /**
599 * This interface must be implemented by activities that contain this
600 * fragment to allow an interaction in this fragment to be communicated
601 * to the activity and potentially other fragments contained in that
602 * activity.
603 * <p/>
604 * See the Android Training lesson <a href=
605 * "http://developer.android.com/training/basics/fragments/communicating.html"
606 * >Communicating with Other Fragments</a> for more information.
607 */
608 public interface OnShareFragmentInteractionListener {
609 void showSearchUsersAndGroups();
610 void refreshUsersOrGroupsListFromServer();
611 void unshareWith(OCShare share);
612 }
613
614 }