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