ShareFileFragment.java 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962
  1. /*
  2. * ownCloud Android client application
  3. *
  4. * @author masensio
  5. * @author David A. Velasco
  6. * @author Juan Carlos González Cabrero
  7. * @author Andy Scherzinger
  8. * Copyright (C) 2015 ownCloud Inc.
  9. * Copyright (C) 2018 Andy Scherzinger
  10. *
  11. * This program is free software: you can redistribute it and/or modify
  12. * it under the terms of the GNU General Public License version 2,
  13. * as published by the Free Software Foundation.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU General Public License
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. */
  23. package com.owncloud.android.ui.fragment;
  24. import android.accounts.Account;
  25. import android.app.Activity;
  26. import android.graphics.Bitmap;
  27. import android.graphics.PorterDuff;
  28. import android.os.Bundle;
  29. import android.view.LayoutInflater;
  30. import android.view.View;
  31. import android.view.ViewGroup;
  32. import android.widget.Button;
  33. import android.widget.CompoundButton;
  34. import android.widget.ImageView;
  35. import android.widget.LinearLayout;
  36. import android.widget.ListView;
  37. import android.widget.ScrollView;
  38. import android.widget.TextView;
  39. import com.google.android.material.button.MaterialButton;
  40. import com.google.android.material.snackbar.Snackbar;
  41. import com.owncloud.android.R;
  42. import com.owncloud.android.datamodel.OCFile;
  43. import com.owncloud.android.datamodel.ThumbnailsCacheManager;
  44. import com.owncloud.android.lib.common.utils.Log_OC;
  45. import com.owncloud.android.lib.resources.shares.OCShare;
  46. import com.owncloud.android.lib.resources.shares.ShareType;
  47. import com.owncloud.android.lib.resources.status.OCCapability;
  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.ui.dialog.SharePasswordDialogFragment;
  52. import com.owncloud.android.utils.DisplayUtils;
  53. import com.owncloud.android.utils.MimeTypeUtil;
  54. import com.owncloud.android.utils.ThemeUtils;
  55. import java.text.SimpleDateFormat;
  56. import java.util.Date;
  57. import java.util.List;
  58. import androidx.annotation.NonNull;
  59. import androidx.appcompat.widget.SwitchCompat;
  60. import androidx.fragment.app.Fragment;
  61. /**
  62. * Fragment for Sharing a file with sharees (users or groups) or creating
  63. * a public link.
  64. *
  65. * A simple {@link Fragment} subclass.
  66. *
  67. * Activities that contain this fragment must implement the
  68. * {@link ShareFragmentListener} interface to handle interaction events.
  69. *
  70. * Use the {@link ShareFileFragment#newInstance} factory method to
  71. * create an instance of this fragment.
  72. */
  73. public class ShareFileFragment extends Fragment implements ShareUserListAdapter.ShareUserAdapterListener {
  74. private static final String TAG = ShareFileFragment.class.getSimpleName();
  75. /**
  76. * The fragment initialization parameters.
  77. */
  78. private static final String ARG_FILE = "FILE";
  79. private static final String ARG_ACCOUNT = "ACCOUNT";
  80. // /** Tag for dialog */
  81. // private static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG";
  82. /**
  83. * File to share, received as a parameter in construction time.
  84. */
  85. private OCFile mFile;
  86. /**
  87. * OC account holding the file to share, received as a parameter in construction time.
  88. */
  89. private Account mAccount;
  90. /**
  91. * Reference to parent listener.
  92. */
  93. private ShareFragmentListener mListener;
  94. /**
  95. * List of private shares bound to the file.
  96. */
  97. private List<OCShare> mPrivateShares;
  98. /**
  99. * Capabilities of the server.
  100. */
  101. private OCCapability mCapabilities;
  102. /**
  103. * Public share bound to the file.
  104. */
  105. private OCShare mPublicShare;
  106. /**
  107. * Listener for changes on switch to share / unshare publicly.
  108. */
  109. private CompoundButton.OnCheckedChangeListener mOnShareViaLinkSwitchCheckedChangeListener;
  110. /**
  111. * Listener for user actions to set, update or clear password on public link.
  112. */
  113. private OnPasswordInteractionListener mOnPasswordInteractionListener;
  114. /**
  115. * Listener for user actions to set, update or clear expiration date on public link.
  116. */
  117. private OnExpirationDateInteractionListener mOnExpirationDateInteractionListener;
  118. /**
  119. * Listener for user actions to set or unset edit permission on public link.
  120. */
  121. private OnEditPermissionInteractionListener mOnEditPermissionInteractionListener;
  122. /**
  123. * Listener for user actions to set or unset hide file listing permission on public link.
  124. */
  125. private OnHideFileListingPermissionInteractionListener mOnHideFileListingPermissionInteractionListener;
  126. /**
  127. * Public factory method to create new ShareFileFragment instances.
  128. *
  129. * @param fileToShare An {@link OCFile} to show in the fragment
  130. * @param account An ownCloud account
  131. * @return A new instance of fragment ShareFileFragment.
  132. */
  133. public static ShareFileFragment newInstance(OCFile fileToShare, Account account) {
  134. ShareFileFragment fragment = new ShareFileFragment();
  135. Bundle args = new Bundle();
  136. args.putParcelable(ARG_FILE, fileToShare);
  137. args.putParcelable(ARG_ACCOUNT, account);
  138. fragment.setArguments(args);
  139. return fragment;
  140. }
  141. @Override
  142. public void onCreate(Bundle savedInstanceState) {
  143. super.onCreate(savedInstanceState);
  144. Log_OC.d(TAG, "onCreate");
  145. if (getArguments() != null) {
  146. mFile = getArguments().getParcelable(ARG_FILE);
  147. mAccount = getArguments().getParcelable(ARG_ACCOUNT);
  148. }
  149. }
  150. @Override
  151. public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  152. Log_OC.d(TAG, "onCreateView");
  153. // use grey as fallback for elements where custom theming is not available
  154. if (ThemeUtils.themingEnabled(getContext())) {
  155. getContext().getTheme().applyStyle(R.style.FallbackThemingTheme, true);
  156. }
  157. int accentColor = ThemeUtils.primaryAccentColor(getContext());
  158. // Inflate the layout for this fragment
  159. View view = inflater.inflate(R.layout.share_file_layout, container, false);
  160. // Setup layout
  161. // Image
  162. ImageView icon = view.findViewById(R.id.shareFileIcon);
  163. icon.setImageDrawable(
  164. MimeTypeUtil.getFileTypeIcon(mFile.getMimeType(), mFile.getFileName(), mAccount, getContext())
  165. );
  166. if (MimeTypeUtil.isImage(mFile)) {
  167. String remoteId = String.valueOf(mFile.getRemoteId());
  168. Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(remoteId);
  169. if (thumbnail != null) {
  170. icon.setImageBitmap(thumbnail);
  171. }
  172. }
  173. // Title
  174. TextView title = view.findViewById(R.id.shareWithUsersSectionTitle);
  175. title.setTextColor(accentColor);
  176. // Name
  177. TextView fileNameHeader = view.findViewById(R.id.shareFileName);
  178. fileNameHeader.setText(getResources().getString(R.string.share_file, mFile.getFileName()));
  179. View headerDivider = view.findViewById(R.id.share_header_divider);
  180. headerDivider.getBackground().setColorFilter(ThemeUtils.primaryAccentColor(getContext()),
  181. PorterDuff.Mode.SRC_ATOP);
  182. // Size
  183. TextView size = view.findViewById(R.id.shareFileSize);
  184. if (mFile.isFolder()) {
  185. size.setVisibility(View.GONE);
  186. } else {
  187. size.setText(DisplayUtils.bytesToHumanReadable(mFile.getFileLength()));
  188. }
  189. // Add User Button
  190. Button addUserGroupButton = view.findViewById(R.id.addUserButton);
  191. addUserGroupButton.getBackground().setColorFilter(accentColor, PorterDuff.Mode.SRC_ATOP);
  192. addUserGroupButton.setOnClickListener(new View.OnClickListener() {
  193. @Override
  194. public void onClick(View view) {
  195. // Show Search Fragment
  196. mListener.showSearchUsersAndGroups();
  197. }
  198. });
  199. // Set listener for user actions on switch for sharing/unsharing via link
  200. initShareViaLinkListener(view);
  201. // Set listener for user actions on expiration date
  202. initExpirationListener(view);
  203. // Set listener for user actions on password
  204. initPasswordListener(view);
  205. // Set listener for user actions on edit permission
  206. initEditPermissionListener(view);
  207. // Set listener for hide file listing
  208. initHideFileListingListener(view);
  209. // Hide share features sections that are not enabled
  210. hideNotEnabledShareSections(view);
  211. return view;
  212. }
  213. /**
  214. * Binds listener for user actions to create or delete a public share
  215. * to the views receiving the user events.
  216. *
  217. * @param shareView Root view in the fragment.
  218. */
  219. private void initShareViaLinkListener(View shareView) {
  220. mOnShareViaLinkSwitchCheckedChangeListener = new OnShareViaLinkListener();
  221. SwitchCompat shareViaLinkSwitch = shareView.findViewById(R.id.shareViaLinkSectionSwitch);
  222. ThemeUtils.tintSwitch(shareViaLinkSwitch, ThemeUtils.primaryAccentColor(getContext()), true);
  223. shareViaLinkSwitch.setOnCheckedChangeListener(mOnShareViaLinkSwitchCheckedChangeListener);
  224. }
  225. /**
  226. * Listener for user actions that create or delete a public share.
  227. */
  228. private class OnShareViaLinkListener
  229. implements CompoundButton.OnCheckedChangeListener {
  230. /**
  231. * Called by R.id.shareViaLinkSectionSwitch to create or delete a public link.
  232. *
  233. * @param switchView {@link SwitchCompat} toggled by the user, R.id.shareViaLinkSectionSwitch
  234. * @param isChecked New switch state.
  235. */
  236. @Override
  237. public void onCheckedChanged(CompoundButton switchView, boolean isChecked) {
  238. if (!isResumed()) {
  239. // very important, setCheched(...) is called automatically during
  240. // Fragment recreation on device rotations
  241. return;
  242. }
  243. if (isChecked) {
  244. if (mCapabilities != null && (mCapabilities.getFilesSharingPublicPasswordEnforced().isTrue() ||
  245. mCapabilities.getFilesSharingPublicAskForOptionalPassword().isTrue())) {
  246. // password enforced by server, request to the user before trying to create
  247. requestPasswordForShareViaLink(true,
  248. mCapabilities.getFilesSharingPublicAskForOptionalPassword().isTrue());
  249. } else {
  250. // create without password if not enforced by server or we don't know if enforced;
  251. ((FileActivity) getActivity()).getFileOperationsHelper().shareFileViaLink(mFile, null);
  252. // ShareActivity#onCreateShareViaLinkOperationFinish will take care if password
  253. // is enforced by the server but app doesn't know, or if server version is
  254. // older than OwnCloudVersion#MINIMUM_VERSION_CAPABILITIES_API
  255. }
  256. } else {
  257. ((FileActivity) getActivity()).getFileOperationsHelper().unshareFileViaLink(mFile);
  258. }
  259. // undo the toggle to grant the view will be correct if any intermediate dialog is cancelled or
  260. // the create/delete operation fails
  261. switchView.setOnCheckedChangeListener(null);
  262. switchView.toggle();
  263. switchView.setOnCheckedChangeListener(mOnShareViaLinkSwitchCheckedChangeListener);
  264. }
  265. }
  266. /**
  267. * Binds listener for user actions that start any update on a expiration date
  268. * for the public link to the views receiving the user events.
  269. *
  270. * @param shareView Root view in the fragment.
  271. */
  272. private void initExpirationListener(View shareView) {
  273. mOnExpirationDateInteractionListener = new OnExpirationDateInteractionListener();
  274. SwitchCompat expirationSwitch = shareView.findViewById(R.id.shareViaLinkExpirationSwitch);
  275. expirationSwitch.setOnCheckedChangeListener(mOnExpirationDateInteractionListener);
  276. ThemeUtils.tintSwitch(expirationSwitch, ThemeUtils.primaryAccentColor(getContext()));
  277. shareView.findViewById(R.id.shareViaLinkExpirationLabel).
  278. setOnClickListener(mOnExpirationDateInteractionListener);
  279. shareView.findViewById(R.id.shareViaLinkExpirationValue).
  280. setOnClickListener(mOnExpirationDateInteractionListener);
  281. }
  282. /**
  283. * Listener for user actions that start any update on the expiration date for the public link.
  284. */
  285. private class OnExpirationDateInteractionListener
  286. implements CompoundButton.OnCheckedChangeListener, View.OnClickListener {
  287. /**
  288. * Called by R.id.shareViaLinkExpirationSwitch to set or clear the expiration date.
  289. *
  290. * @param switchView {@link SwitchCompat} toggled by the user, R.id.shareViaLinkExpirationSwitch
  291. * @param isChecked New switch state.
  292. */
  293. @Override
  294. public void onCheckedChanged(CompoundButton switchView, boolean isChecked) {
  295. if (!isResumed()) {
  296. // very important, setCheched(...) is called automatically during
  297. // Fragment recreation on device rotations
  298. return;
  299. }
  300. if (isChecked) {
  301. ExpirationDatePickerDialogFragment dialog = ExpirationDatePickerDialogFragment.newInstance(mFile, -1);
  302. dialog.show(
  303. getActivity().getSupportFragmentManager(),
  304. ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG
  305. );
  306. } else {
  307. ((FileActivity) getActivity()).getFileOperationsHelper().setExpirationDateToShareViaLink(mFile, -1);
  308. }
  309. // undo the toggle to grant the view will be correct if the dialog is cancelled
  310. switchView.setOnCheckedChangeListener(null);
  311. switchView.toggle();
  312. switchView.setOnCheckedChangeListener(mOnExpirationDateInteractionListener);
  313. }
  314. /**
  315. * Called by R.id.shareViaLinkExpirationLabel or R.id.shareViaLinkExpirationValue to change the current
  316. * expiration date.
  317. *
  318. * @param expirationView Label or value view touched by the user.
  319. */
  320. @Override
  321. public void onClick(View expirationView) {
  322. if (mPublicShare != null && mPublicShare.getExpirationDate() > 0) {
  323. long chosenDateInMillis = mPublicShare.getExpirationDate();
  324. ExpirationDatePickerDialogFragment dialog =
  325. ExpirationDatePickerDialogFragment.newInstance(mFile, chosenDateInMillis);
  326. dialog.show(
  327. getActivity().getSupportFragmentManager(),
  328. ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG
  329. );
  330. }
  331. }
  332. }
  333. /**
  334. * Binds listener for user actions that start any update on a password for the public link to the views receiving
  335. * the user events.
  336. *
  337. * @param shareView Root view in the fragment.
  338. */
  339. private void initPasswordListener(View shareView) {
  340. mOnPasswordInteractionListener = new OnPasswordInteractionListener();
  341. SwitchCompat passwordSwitch = shareView.findViewById(R.id.shareViaLinkPasswordSwitch);
  342. passwordSwitch.setOnCheckedChangeListener(mOnPasswordInteractionListener);
  343. ThemeUtils.tintSwitch(passwordSwitch, ThemeUtils.primaryAccentColor(getContext()));
  344. shareView.findViewById(R.id.shareViaLinkPasswordLabel).setOnClickListener(mOnPasswordInteractionListener);
  345. shareView.findViewById(R.id.shareViaLinkPasswordValue).setOnClickListener(mOnPasswordInteractionListener);
  346. }
  347. /**
  348. * Listener for user actions that start any update on a password for the public link.
  349. */
  350. private class OnPasswordInteractionListener
  351. implements CompoundButton.OnCheckedChangeListener, View.OnClickListener {
  352. /**
  353. * Called by R.id.shareViaLinkPasswordSwitch to set or clear the password.
  354. *
  355. * @param switchView {@link SwitchCompat} toggled by the user, R.id.shareViaLinkPasswordSwitch
  356. * @param isChecked New switch state.
  357. */
  358. @Override
  359. public void onCheckedChanged(CompoundButton switchView, boolean isChecked) {
  360. if (!isResumed()) {
  361. // very important, setChecked(...) is called automatically during
  362. // Fragment recreation on device rotations
  363. return;
  364. }
  365. if (isChecked) {
  366. requestPasswordForShareViaLink(false,
  367. mCapabilities.getFilesSharingPublicAskForOptionalPassword().isTrue());
  368. } else {
  369. ((FileActivity) getActivity()).getFileOperationsHelper().setPasswordToShareViaLink(mFile, ""); // clears
  370. }
  371. // undo the toggle to grant the view will be correct if the dialog is cancelled
  372. switchView.setOnCheckedChangeListener(null);
  373. switchView.toggle();
  374. switchView.setOnCheckedChangeListener(mOnPasswordInteractionListener);
  375. }
  376. /**
  377. * Called by R.id.shareViaLinkPasswordLabel or R.id.shareViaLinkPasswordValue
  378. * to change the current password.
  379. *
  380. * @param passwordView Label or value view touched by the user.
  381. */
  382. @Override
  383. public void onClick(View passwordView) {
  384. if (mPublicShare != null && mPublicShare.isPasswordProtected()) {
  385. requestPasswordForShareViaLink(false,
  386. mCapabilities.getFilesSharingPublicAskForOptionalPassword().isTrue());
  387. }
  388. }
  389. }
  390. /**
  391. * Binds listener for user actions that start any update the edit permissions
  392. * for the public link to the views receiving the user events.
  393. *
  394. * @param shareView Root view in the fragment.
  395. */
  396. private void initEditPermissionListener(View shareView) {
  397. mOnEditPermissionInteractionListener = new OnEditPermissionInteractionListener();
  398. SwitchCompat permissionSwitch = shareView.findViewById(R.id.shareViaLinkEditPermissionSwitch);
  399. permissionSwitch.setOnCheckedChangeListener(mOnEditPermissionInteractionListener);
  400. ThemeUtils.tintSwitch(permissionSwitch, ThemeUtils.primaryAccentColor(getContext()));
  401. }
  402. /**
  403. * Binds listener for user actions that start any update the hide file listing permissions
  404. * for the public link to the views receiving the user events.
  405. *
  406. * @param shareView Root view in the fragment.
  407. */
  408. private void initHideFileListingListener(View shareView) {
  409. mOnHideFileListingPermissionInteractionListener = new OnHideFileListingPermissionInteractionListener();
  410. SwitchCompat permissionSwitch = shareView.findViewById(R.id.shareViaLinkFileListingPermissionSwitch);
  411. permissionSwitch.setOnCheckedChangeListener(mOnHideFileListingPermissionInteractionListener);
  412. ThemeUtils.tintSwitch(permissionSwitch, ThemeUtils.primaryAccentColor(getContext()));
  413. }
  414. /**
  415. * Listener for user actions that start any update on the edit permissions for the public link.
  416. */
  417. private class OnEditPermissionInteractionListener implements CompoundButton.OnCheckedChangeListener {
  418. /**
  419. * Called by R.id.shareViaLinkEditPermissionSwitch to set or clear the edit permission.
  420. *
  421. * @param switchView {@link SwitchCompat} toggled by the user, R.id.shareViaLinkEditPermissionSwitch
  422. * @param isChecked New switch state.
  423. */
  424. @Override
  425. public void onCheckedChanged(CompoundButton switchView, boolean isChecked) {
  426. if (!isResumed()) {
  427. // very important, setCheched(...) is called automatically during
  428. // Fragment recreation on device rotations
  429. return;
  430. }
  431. ((FileActivity) getActivity()).getFileOperationsHelper().setUploadPermissionsToShare(mFile, isChecked);
  432. // undo the toggle to grant the view will be correct if the dialog is cancelled
  433. switchView.setOnCheckedChangeListener(null);
  434. switchView.toggle();
  435. switchView.setOnCheckedChangeListener(mOnEditPermissionInteractionListener);
  436. }
  437. }
  438. /**
  439. * Listener for user actions that start any update on the hide file listing permissions for the public link.
  440. */
  441. private class OnHideFileListingPermissionInteractionListener implements CompoundButton.OnCheckedChangeListener {
  442. /**
  443. * Called by R.id.shareViaLinkHideListPermissionSwitch to set or clear the edit permission.
  444. *
  445. * @param switchView {@link SwitchCompat} toggled by the user, R.id.shareViaLinkHideListPermissionSwitch
  446. * @param isChecked New switch state.
  447. */
  448. @Override
  449. public void onCheckedChanged(CompoundButton switchView, boolean isChecked) {
  450. if (!isResumed()) {
  451. // very important, setChecked(...) is called automatically during
  452. // Fragment recreation on device rotations
  453. return;
  454. }
  455. if (mCapabilities.getFilesFileDrop().isTrue()) {
  456. ((FileActivity) getActivity()).getFileOperationsHelper().
  457. setHideFileListingPermissionsToShare(
  458. mPublicShare,
  459. isChecked
  460. );
  461. } else {
  462. // not supported in ownCloud
  463. Snackbar.make(getView(), R.string.files_drop_not_supported, Snackbar.LENGTH_LONG)
  464. .setAction(R.string.learn_more, v ->
  465. DisplayUtils.startLinkIntent(requireActivity(), R.string.url_server_install))
  466. .show();
  467. }
  468. // undo the toggle to grant the view will be correct if the dialog is cancelled
  469. switchView.setOnCheckedChangeListener(null);
  470. switchView.toggle();
  471. switchView.setOnCheckedChangeListener(mOnHideFileListingPermissionInteractionListener);
  472. }
  473. }
  474. @Override
  475. public void onActivityCreated(Bundle savedInstanceState) {
  476. super.onActivityCreated(savedInstanceState);
  477. Log_OC.d(TAG, "onActivityCreated");
  478. // Load known capabilities of the server from DB
  479. refreshCapabilitiesFromDB();
  480. // Load data into the list of private shares
  481. refreshUsersOrGroupsListFromDB();
  482. // Load data of public share, if exists
  483. refreshPublicShareFromDB();
  484. }
  485. @Override
  486. public void onAttach(Activity activity) {
  487. super.onAttach(activity);
  488. try {
  489. mListener = (ShareFragmentListener) activity;
  490. } catch (ClassCastException e) {
  491. throw new IllegalArgumentException(
  492. activity.toString() + " must implement OnShareFragmentInteractionListener", e);
  493. }
  494. }
  495. @Override
  496. public void onDetach() {
  497. super.onDetach();
  498. mListener = null;
  499. }
  500. /**
  501. * Get known server capabilities from DB
  502. * <p/>
  503. * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
  504. * instance ready to use. If not ready, does nothing.
  505. */
  506. public void refreshCapabilitiesFromDB() {
  507. if (((FileActivity) mListener).getStorageManager() != null) {
  508. mCapabilities = ((FileActivity) mListener).getStorageManager().getCapability(mAccount.name);
  509. }
  510. }
  511. /**
  512. * Get users and groups from the DB to fill in the "share with" list.
  513. * <p/>
  514. * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
  515. * instance ready to use. If not ready, does nothing.
  516. */
  517. public void refreshUsersOrGroupsListFromDB() {
  518. if (((FileActivity) mListener).getStorageManager() != null) {
  519. // Get Users and Groups
  520. mPrivateShares = ((FileActivity) mListener).getStorageManager().getSharesWithForAFile(
  521. mFile.getRemotePath(),
  522. mAccount.name
  523. );
  524. // Update list of users/groups
  525. updateListOfUserGroups();
  526. }
  527. }
  528. private void updateListOfUserGroups() {
  529. // Update list of users/groups
  530. // TODO Refactoring: create a new {@link ShareUserListAdapter} instance with every call should not be needed
  531. ShareUserListAdapter mUserGroupsAdapter = new ShareUserListAdapter(
  532. getActivity(),
  533. R.layout.share_user_item,
  534. mPrivateShares,
  535. this
  536. );
  537. // Show data
  538. TextView noShares = getView().findViewById(R.id.shareNoUsers);
  539. ListView usersList = getView().findViewById(R.id.shareUsersList);
  540. if (mPrivateShares.size() > 0) {
  541. noShares.setVisibility(View.GONE);
  542. usersList.setVisibility(View.VISIBLE);
  543. usersList.setAdapter(mUserGroupsAdapter);
  544. } else {
  545. noShares.setVisibility(View.VISIBLE);
  546. usersList.setVisibility(View.GONE);
  547. }
  548. // Set Scroll to initial position
  549. ScrollView scrollView = getView().findViewById(R.id.shareScroll);
  550. scrollView.scrollTo(0, 0);
  551. }
  552. @Override
  553. public void unshareButtonPressed(OCShare share) {
  554. // Unshare
  555. Log_OC.d(TAG, "Unsharing " + share.getSharedWithDisplayName());
  556. mListener.unshareWith(share);
  557. }
  558. @Override
  559. public void editShare(OCShare share) {
  560. // move to fragment to edit share
  561. Log_OC.d(TAG, "Editing " + share.getSharedWithDisplayName());
  562. mListener.showEditShare(share);
  563. }
  564. /**
  565. * Get public link from the DB to fill in the "Share link" section in the UI.
  566. * <p/>
  567. * Takes into account server capabilities before reading database.
  568. * <p/>
  569. * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
  570. * instance ready to use. If not ready, does nothing.
  571. */
  572. public void refreshPublicShareFromDB() {
  573. if (isPublicShareDisabled()) {
  574. hidePublicShare();
  575. } else if (((FileActivity) mListener).getStorageManager() != null) {
  576. // Get public share
  577. mPublicShare = ((FileActivity) mListener).getStorageManager().getFirstShareByPathAndType(
  578. mFile.getRemotePath(),
  579. ShareType.PUBLIC_LINK,
  580. ""
  581. );
  582. // Update public share section
  583. updatePublicShareSection();
  584. }
  585. }
  586. /**
  587. * @return 'True' when public share is disabled in the server
  588. */
  589. private boolean isPublicShareDisabled() {
  590. return mCapabilities != null && mCapabilities.getFilesSharingPublicEnabled().isFalse();
  591. }
  592. /**
  593. * Updates in the UI the section about public share with the information in the current public share bound to
  594. * mFile, if any.
  595. */
  596. private void updatePublicShareSection() {
  597. if (mPublicShare != null && ShareType.PUBLIC_LINK.equals(mPublicShare.getShareType())) {
  598. // public share bound -> expand section
  599. updateShareViaLinkSwitch(mOnShareViaLinkSwitchCheckedChangeListener);
  600. getExpirationDateSection().setVisibility(View.VISIBLE);
  601. getPasswordSection().setVisibility(View.VISIBLE);
  602. if (mFile.isFolder() && !mCapabilities.getFilesSharingPublicUpload().isFalse()) {
  603. getEditPermissionSection().setVisibility(View.VISIBLE);
  604. getHideFileListingPermissionSection().setVisibility(View.VISIBLE);
  605. } else {
  606. getEditPermissionSection().setVisibility(View.GONE);
  607. }
  608. // init link button
  609. initLinkButton();
  610. /// update state of expiration date switch
  611. updateExpirationDateSwitch(mPublicShare.getExpirationDate(), mOnExpirationDateInteractionListener);
  612. /// update state of password switch
  613. updatePasswordSwitch(mPublicShare.isPasswordProtected(), mOnPasswordInteractionListener);
  614. /// update state of the edit permission switch
  615. updatePermissionSwitch(mPublicShare.getPermissions(), mOnEditPermissionInteractionListener);
  616. /// update state of the hide file listing permission switch
  617. updateHideFileListingPermissionSwitch(mPublicShare.getPermissions(),
  618. mOnHideFileListingPermissionInteractionListener);
  619. } else {
  620. // no public share -> collapse section
  621. collapsePublicShareSection();
  622. }
  623. }
  624. private void updateShareViaLinkSwitch(
  625. CompoundButton.OnCheckedChangeListener onShareViaLinkSwitchCheckedChangeListener) {
  626. SwitchCompat shareViaLinkSwitch = getShareViaLinkSwitch();
  627. if (!shareViaLinkSwitch.isChecked()) {
  628. // set null listener before setChecked() to prevent infinite loop of calls
  629. shareViaLinkSwitch.setOnCheckedChangeListener(null);
  630. shareViaLinkSwitch.setChecked(true);
  631. shareViaLinkSwitch.setOnCheckedChangeListener(onShareViaLinkSwitchCheckedChangeListener);
  632. }
  633. }
  634. private void updateHideFileListingPermissionSwitch(
  635. int permissions,
  636. OnHideFileListingPermissionInteractionListener onHideFileListingPermissionInteractionListener)
  637. {
  638. SwitchCompat hideFileListingPermissionSwitch = getHideFileListingPermissionSwitch();
  639. // set null listener before setChecked() to prevent infinite loop of calls
  640. hideFileListingPermissionSwitch.setOnCheckedChangeListener(null);
  641. boolean readOnly = (permissions & OCShare.READ_PERMISSION_FLAG) != 0;
  642. hideFileListingPermissionSwitch.setChecked(!readOnly);
  643. // recover listener
  644. hideFileListingPermissionSwitch.setOnCheckedChangeListener(onHideFileListingPermissionInteractionListener);
  645. }
  646. private void updatePermissionSwitch(
  647. int permissions,
  648. OnEditPermissionInteractionListener onEditPermissionInteractionListener)
  649. {
  650. SwitchCompat editPermissionSwitch = getEditPermissionSwitch();
  651. // set null listener before setChecked() to prevent infinite loop of calls
  652. editPermissionSwitch.setOnCheckedChangeListener(null);
  653. if (permissions > OCShare.READ_PERMISSION_FLAG) {
  654. if (!editPermissionSwitch.isChecked()) {
  655. editPermissionSwitch.toggle();
  656. }
  657. getHideFileListingPermissionSection().setVisibility(View.VISIBLE);
  658. } else {
  659. if (editPermissionSwitch.isChecked()) {
  660. editPermissionSwitch.toggle();
  661. }
  662. getHideFileListingPermissionSection().setVisibility(View.GONE);
  663. }
  664. // recover listener
  665. editPermissionSwitch.setOnCheckedChangeListener(onEditPermissionInteractionListener);
  666. }
  667. private void updatePasswordSwitch(
  668. boolean isPasswordProtected,
  669. OnPasswordInteractionListener onPasswordInteractionListener)
  670. {
  671. SwitchCompat passwordSwitch = getPasswordSwitch();
  672. // set null listener before setChecked() to prevent infinite loop of calls
  673. passwordSwitch.setOnCheckedChangeListener(null);
  674. if (isPasswordProtected) {
  675. if (!passwordSwitch.isChecked()) {
  676. passwordSwitch.toggle();
  677. }
  678. getPasswordValue().setVisibility(View.VISIBLE);
  679. } else {
  680. if (passwordSwitch.isChecked()) {
  681. passwordSwitch.toggle();
  682. }
  683. getPasswordValue().setVisibility(View.INVISIBLE);
  684. }
  685. // recover listener
  686. passwordSwitch.setOnCheckedChangeListener(onPasswordInteractionListener);
  687. }
  688. private void updateExpirationDateSwitch(
  689. long expirationDate,
  690. OnExpirationDateInteractionListener onExpirationDateInteractionListener)
  691. {
  692. SwitchCompat expirationDateSwitch = getExpirationDateSwitch();
  693. // set null listener before setChecked() to prevent infinite loop of calls
  694. expirationDateSwitch.setOnCheckedChangeListener(null);
  695. if (expirationDate > 0) {
  696. if (!expirationDateSwitch.isChecked()) {
  697. expirationDateSwitch.toggle();
  698. }
  699. String formattedDate = SimpleDateFormat.getDateInstance().format(new Date(expirationDate));
  700. getExpirationDateValue().setText(formattedDate);
  701. } else {
  702. if (expirationDateSwitch.isChecked()) {
  703. expirationDateSwitch.toggle();
  704. }
  705. getExpirationDateValue().setText(R.string.empty);
  706. }
  707. // recover listener
  708. expirationDateSwitch.setOnCheckedChangeListener(onExpirationDateInteractionListener);
  709. }
  710. private void initLinkButton() {
  711. MaterialButton getLinkButton = getGetLinkButton();
  712. getLinkButton.getBackground().setColorFilter(ThemeUtils.primaryColor(getContext()),
  713. PorterDuff.Mode.SRC_ATOP);
  714. getLinkButton.setVisibility(View.VISIBLE);
  715. getLinkButton.setOnClickListener(new View.OnClickListener() {
  716. @Override
  717. public void onClick(View v) {
  718. //GetLink from the server and show ShareLinkToDialog
  719. ((FileActivity) getActivity()).getFileOperationsHelper().
  720. getFileWithLink(mFile);
  721. }
  722. });
  723. }
  724. private void collapsePublicShareSection() {
  725. SwitchCompat shareViaLinkSwitch = getShareViaLinkSwitch();
  726. if (shareViaLinkSwitch.isChecked()) {
  727. shareViaLinkSwitch.setOnCheckedChangeListener(null);
  728. getShareViaLinkSwitch().setChecked(false);
  729. shareViaLinkSwitch.setOnCheckedChangeListener(
  730. mOnShareViaLinkSwitchCheckedChangeListener
  731. );
  732. }
  733. getExpirationDateSection().setVisibility(View.GONE);
  734. getPasswordSection().setVisibility(View.GONE);
  735. getEditPermissionSection().setVisibility(View.GONE);
  736. getHideFileListingPermissionSection().setVisibility(View.GONE);
  737. getGetLinkButton().setVisibility(View.GONE);
  738. }
  739. // BEWARE: following methods will fail with NullPointerException if called before onCreateView() finishes
  740. private SwitchCompat getShareViaLinkSwitch() {
  741. return (SwitchCompat) getView().findViewById(R.id.shareViaLinkSectionSwitch);
  742. }
  743. private View getExpirationDateSection() {
  744. return getView().findViewById(R.id.shareViaLinkExpirationSection);
  745. }
  746. private SwitchCompat getExpirationDateSwitch() {
  747. return (SwitchCompat) getView().findViewById(R.id.shareViaLinkExpirationSwitch);
  748. }
  749. private TextView getExpirationDateValue() {
  750. return (TextView) getView().findViewById(R.id.shareViaLinkExpirationValue);
  751. }
  752. private View getPasswordSection() {
  753. return getView().findViewById(R.id.shareViaLinkPasswordSection);
  754. }
  755. private SwitchCompat getPasswordSwitch() {
  756. return (SwitchCompat) getView().findViewById(R.id.shareViaLinkPasswordSwitch);
  757. }
  758. private TextView getPasswordValue() {
  759. return (TextView) getView().findViewById(R.id.shareViaLinkPasswordValue);
  760. }
  761. private View getEditPermissionSection() {
  762. return getView().findViewById(R.id.shareViaLinkEditPermissionSection);
  763. }
  764. private SwitchCompat getEditPermissionSwitch() {
  765. return (SwitchCompat) getView().findViewById(R.id.shareViaLinkEditPermissionSwitch);
  766. }
  767. private SwitchCompat getHideFileListingPermissionSwitch() {
  768. return (SwitchCompat) getView().findViewById(R.id.shareViaLinkFileListingPermissionSwitch);
  769. }
  770. private View getHideFileListingPermissionSection() {
  771. return getView().findViewById(R.id.shareViaLinkHideFileListingPermissionSection);
  772. }
  773. private MaterialButton getGetLinkButton() {
  774. return (MaterialButton) getView().findViewById(R.id.shareViaLinkGetLinkButton);
  775. }
  776. /**
  777. * Hides all the UI elements related to public share
  778. */
  779. private void hidePublicShare() {
  780. getShareViaLinkSwitch().setVisibility(View.GONE);
  781. getExpirationDateSection().setVisibility(View.GONE);
  782. getPasswordSection().setVisibility(View.GONE);
  783. getEditPermissionSection().setVisibility(View.GONE);
  784. getGetLinkButton().setVisibility(View.GONE);
  785. getHideFileListingPermissionSection().setVisibility(View.GONE);
  786. }
  787. /**
  788. * Starts a dialog that requests a password to the user to protect a share link.
  789. *
  790. * @param createShare When 'true', the request for password will be followed by the creation of a new public
  791. * link; when 'false', a public share is assumed to exist, and the password is bound to it.
  792. * @param askForPassword if true, password is optional
  793. */
  794. public void requestPasswordForShareViaLink(boolean createShare, boolean askForPassword) {
  795. SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(mFile,
  796. createShare,
  797. askForPassword);
  798. dialog.show(getFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT);
  799. }
  800. /**
  801. * Hide share features sections that are not enabled
  802. *
  803. * @param view share file view
  804. */
  805. private void hideNotEnabledShareSections(View view) {
  806. LinearLayout shareWithUsersSection = view.findViewById(R.id.shareWithUsersSection);
  807. LinearLayout shareViaLinkSection = view.findViewById(R.id.shareViaLinkSection);
  808. boolean shareViaLinkAllowed = getActivity().getResources().getBoolean(R.bool.share_via_link_feature);
  809. boolean shareWithUsersAllowed = getActivity().getResources().getBoolean(R.bool.share_with_users_feature);
  810. // Hide share via link section if it is not enabled
  811. if (!shareViaLinkAllowed) {
  812. shareViaLinkSection.setVisibility(View.GONE);
  813. }
  814. // Hide share with users section if it is not enabled
  815. if (!shareWithUsersAllowed) {
  816. shareWithUsersSection.setVisibility(View.GONE);
  817. }
  818. }
  819. }