DrawerActivity.java 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Andy Scherzinger
  5. * @author Tobias Kaminsky
  6. * @author Chris Narkiewicz <hello@ezaquarii.com>
  7. * Copyright (C) 2016 Andy Scherzinger
  8. * Copyright (C) 2017 Tobias Kaminsky
  9. * Copyright (C) 2016 Nextcloud
  10. * Copyright (C) 2016 ownCloud Inc.
  11. * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  12. * Copyright (C) 2020 Infomaniak Network SA
  13. *
  14. * This program is free software; you can redistribute it and/or
  15. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  16. * License as published by the Free Software Foundation; either
  17. * version 3 of the License, or any later version.
  18. *
  19. * This program is distributed in the hope that it will be useful,
  20. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  23. *
  24. * You should have received a copy of the GNU Affero General Public
  25. * License along with this program. If not, see <http://www.gnu.org/licenses/>.
  26. */
  27. package com.owncloud.android.ui.activity;
  28. import android.app.Activity;
  29. import android.content.Context;
  30. import android.content.Intent;
  31. import android.content.res.Configuration;
  32. import android.graphics.drawable.Drawable;
  33. import android.net.Uri;
  34. import android.os.Build;
  35. import android.os.Bundle;
  36. import android.os.Handler;
  37. import android.os.SystemClock;
  38. import android.view.Menu;
  39. import android.view.MenuItem;
  40. import android.view.View;
  41. import android.widget.FrameLayout;
  42. import android.widget.ImageView;
  43. import android.widget.LinearLayout;
  44. import android.widget.ProgressBar;
  45. import android.widget.TextView;
  46. import com.bumptech.glide.request.animation.GlideAnimation;
  47. import com.bumptech.glide.request.target.SimpleTarget;
  48. import com.google.android.material.button.MaterialButton;
  49. import com.google.android.material.navigation.NavigationView;
  50. import com.nextcloud.client.account.User;
  51. import com.nextcloud.client.di.Injectable;
  52. import com.nextcloud.client.network.ClientFactory;
  53. import com.nextcloud.client.onboarding.FirstRunActivity;
  54. import com.nextcloud.client.preferences.AppPreferences;
  55. import com.nextcloud.client.preferences.DarkMode;
  56. import com.nextcloud.java.util.Optional;
  57. import com.nextcloud.ui.ChooseAccountDialogFragment;
  58. import com.owncloud.android.MainApp;
  59. import com.owncloud.android.R;
  60. import com.owncloud.android.authentication.PassCodeManager;
  61. import com.owncloud.android.datamodel.ArbitraryDataProvider;
  62. import com.owncloud.android.datamodel.ExternalLinksProvider;
  63. import com.owncloud.android.datamodel.FileDataStorageManager;
  64. import com.owncloud.android.datamodel.OCFile;
  65. import com.owncloud.android.lib.common.ExternalLink;
  66. import com.owncloud.android.lib.common.ExternalLinkType;
  67. import com.owncloud.android.lib.common.Quota;
  68. import com.owncloud.android.lib.common.UserInfo;
  69. import com.owncloud.android.lib.common.accounts.ExternalLinksOperation;
  70. import com.owncloud.android.lib.common.operations.RemoteOperation;
  71. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  72. import com.owncloud.android.lib.common.utils.Log_OC;
  73. import com.owncloud.android.lib.resources.files.SearchRemoteOperation;
  74. import com.owncloud.android.lib.resources.status.OCCapability;
  75. import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation;
  76. import com.owncloud.android.operations.GetCapabilitiesOperation;
  77. import com.owncloud.android.ui.activities.ActivitiesActivity;
  78. import com.owncloud.android.ui.events.AccountRemovedEvent;
  79. import com.owncloud.android.ui.events.ChangeMenuEvent;
  80. import com.owncloud.android.ui.events.DummyDrawerEvent;
  81. import com.owncloud.android.ui.events.SearchEvent;
  82. import com.owncloud.android.ui.fragment.OCFileListFragment;
  83. import com.owncloud.android.ui.fragment.PhotoFragment;
  84. import com.owncloud.android.ui.trashbin.TrashbinActivity;
  85. import com.owncloud.android.utils.DisplayUtils;
  86. import com.owncloud.android.utils.DrawerMenuUtil;
  87. import com.owncloud.android.utils.FilesSyncHelper;
  88. import com.owncloud.android.utils.ThemeUtils;
  89. import com.owncloud.android.utils.svg.MenuSimpleTarget;
  90. import org.greenrobot.eventbus.EventBus;
  91. import org.greenrobot.eventbus.Subscribe;
  92. import org.greenrobot.eventbus.ThreadMode;
  93. import org.parceler.Parcels;
  94. import java.util.ArrayList;
  95. import java.util.List;
  96. import javax.inject.Inject;
  97. import androidx.annotation.NonNull;
  98. import androidx.appcompat.app.ActionBarDrawerToggle;
  99. import androidx.appcompat.app.AppCompatDelegate;
  100. import androidx.core.content.ContextCompat;
  101. import androidx.core.view.GravityCompat;
  102. import androidx.drawerlayout.widget.DrawerLayout;
  103. /**
  104. * Base class to handle setup of the drawer implementation including user switching and avatar fetching and fallback
  105. * generation.
  106. */
  107. public abstract class DrawerActivity extends ToolbarActivity
  108. implements DisplayUtils.AvatarGenerationListener, Injectable {
  109. private static final String TAG = DrawerActivity.class.getSimpleName();
  110. private static final String KEY_IS_ACCOUNT_CHOOSER_ACTIVE = "IS_ACCOUNT_CHOOSER_ACTIVE";
  111. private static final String KEY_CHECKED_MENU_ITEM = "CHECKED_MENU_ITEM";
  112. private static final int ACTION_MANAGE_ACCOUNTS = 101;
  113. private static final int MENU_ORDER_EXTERNAL_LINKS = 3;
  114. private static final int MENU_ITEM_EXTERNAL_LINK = 111;
  115. /**
  116. * Reference to the drawer layout.
  117. */
  118. private DrawerLayout mDrawerLayout;
  119. /**
  120. * Reference to the drawer toggle.
  121. */
  122. protected ActionBarDrawerToggle mDrawerToggle;
  123. /**
  124. * Reference to the navigation view.
  125. */
  126. private NavigationView mNavigationView;
  127. /**
  128. * Reference to the navigation view header.
  129. */
  130. private View mNavigationViewHeader;
  131. /**
  132. * Flag to signal if the account chooser is active.
  133. */
  134. private boolean mIsAccountChooserActive;
  135. /**
  136. * Id of the checked menu item.
  137. */
  138. private int mCheckedMenuItem = Menu.NONE;
  139. /**
  140. * container layout of the quota view.
  141. */
  142. private LinearLayout mQuotaView;
  143. /**
  144. * progress bar of the quota view.
  145. */
  146. private ProgressBar mQuotaProgressBar;
  147. /**
  148. * text view of the quota view.
  149. */
  150. private TextView mQuotaTextPercentage;
  151. private TextView mQuotaTextLink;
  152. /**
  153. * runnable that will be executed after the drawer has been closed.
  154. */
  155. private Runnable pendingRunnable;
  156. private ExternalLinksProvider externalLinksProvider;
  157. private ArbitraryDataProvider arbitraryDataProvider;
  158. @Inject
  159. AppPreferences preferences;
  160. @Inject
  161. ClientFactory clientFactory;
  162. /**
  163. * Initializes the drawer, its content and highlights the menu item with the given id. This method needs to be
  164. * called after the content view has been set.
  165. *
  166. * @param menuItemId the menu item to be checked/highlighted
  167. */
  168. protected void setupDrawer(int menuItemId) {
  169. setupDrawer();
  170. setDrawerMenuItemChecked(menuItemId);
  171. }
  172. /**
  173. * Initializes the drawer and its content. This method needs to be called after the content view has been set.
  174. */
  175. protected void setupDrawer() {
  176. mDrawerLayout = findViewById(R.id.drawer_layout);
  177. mNavigationView = findViewById(R.id.nav_view);
  178. if (mNavigationView != null) {
  179. // Setting up drawer header
  180. mNavigationViewHeader = mNavigationView.getHeaderView(0);
  181. FrameLayout drawerHeader = mNavigationViewHeader.findViewById(R.id.drawer_header_view);
  182. setupDrawerHeader(drawerHeader);
  183. setupDrawerMenu(mNavigationView);
  184. getAndDisplayUserQuota();
  185. setupQuotaElement();
  186. }
  187. setupDrawerToggle();
  188. if (getSupportActionBar() != null) {
  189. getSupportActionBar().setDisplayHomeAsUpEnabled(true);
  190. }
  191. }
  192. /**
  193. * initializes and sets up the drawer toggle.
  194. */
  195. private void setupDrawerToggle() {
  196. mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.drawer_open, R.string.drawer_close) {
  197. /** Called when a drawer has settled in a completely closed state. */
  198. public void onDrawerClosed(View view) {
  199. super.onDrawerClosed(view);
  200. supportInvalidateOptionsMenu();
  201. mDrawerToggle.setDrawerIndicatorEnabled(isDrawerIndicatorAvailable());
  202. if (pendingRunnable != null) {
  203. new Handler().post(pendingRunnable);
  204. pendingRunnable = null;
  205. }
  206. closeDrawer();
  207. }
  208. /** Called when a drawer has settled in a completely open state. */
  209. public void onDrawerOpened(View drawerView) {
  210. super.onDrawerOpened(drawerView);
  211. mDrawerToggle.setDrawerIndicatorEnabled(true);
  212. supportInvalidateOptionsMenu();
  213. }
  214. };
  215. // Set the drawer toggle as the DrawerListener
  216. mDrawerLayout.addDrawerListener(mDrawerToggle);
  217. mDrawerToggle.setDrawerIndicatorEnabled(true);
  218. mDrawerToggle.setDrawerSlideAnimationEnabled(true);
  219. Drawable backArrow = getResources().getDrawable(R.drawable.ic_arrow_back);
  220. mDrawerToggle.setHomeAsUpIndicator(ThemeUtils.tintDrawable(backArrow, ThemeUtils.appBarPrimaryFontColor(this)));
  221. mDrawerToggle.getDrawerArrowDrawable().setColor(ThemeUtils.appBarPrimaryFontColor(this));
  222. }
  223. /**
  224. * setup quota elements of the drawer.
  225. */
  226. private void setupQuotaElement() {
  227. mQuotaView = (LinearLayout) findQuotaViewById(R.id.drawer_quota);
  228. mQuotaProgressBar = (ProgressBar) findQuotaViewById(R.id.drawer_quota_ProgressBar);
  229. mQuotaTextPercentage = (TextView) findQuotaViewById(R.id.drawer_quota_percentage);
  230. mQuotaTextLink = (TextView) findQuotaViewById(R.id.drawer_quota_link);
  231. ThemeUtils.colorProgressBar(mQuotaProgressBar, ThemeUtils.primaryColor(this));
  232. }
  233. /**
  234. * setup drawer header, basically the logo color
  235. */
  236. private void setupDrawerHeader(FrameLayout drawerHeader) {
  237. drawerHeader.setBackgroundColor(ThemeUtils.primaryColor(getAccount(), true, this));
  238. }
  239. /**
  240. * setup drawer content, basically setting the item selected listener.
  241. *
  242. * @param navigationView the drawers navigation view
  243. */
  244. private void setupDrawerMenu(NavigationView navigationView) {
  245. navigationView.setItemIconTintList(null);
  246. // setup actions for drawer menu items
  247. navigationView.setNavigationItemSelectedListener(
  248. menuItem -> {
  249. mDrawerLayout.closeDrawers();
  250. // pending runnable will be executed after the drawer has been closed
  251. pendingRunnable = () -> onNavigationItemClicked(menuItem);
  252. return true;
  253. });
  254. User account = accountManager.getUser();
  255. filterDrawerMenu(navigationView.getMenu(), account);
  256. }
  257. private void filterDrawerMenu(final Menu menu, @NonNull final User user) {
  258. FileDataStorageManager storageManager = new FileDataStorageManager(user.toPlatformAccount(),
  259. getContentResolver());
  260. OCCapability capability = storageManager.getCapability(user.getAccountName());
  261. DrawerMenuUtil.filterSearchMenuItems(menu, user, getResources(), true);
  262. DrawerMenuUtil.filterTrashbinMenuItem(menu, user, capability);
  263. DrawerMenuUtil.filterActivityMenuItem(menu, capability);
  264. DrawerMenuUtil.setupHomeMenuItem(menu, getResources());
  265. DrawerMenuUtil.removeMenuItem(menu, R.id.nav_community,
  266. !getResources().getBoolean(R.bool.participate_enabled));
  267. DrawerMenuUtil.removeMenuItem(menu, R.id.nav_shared, !getResources().getBoolean(R.bool.shared_enabled));
  268. DrawerMenuUtil.removeMenuItem(menu, R.id.nav_contacts, !getResources().getBoolean(R.bool.contacts_backup)
  269. || !getResources().getBoolean(R.bool.show_drawer_contacts_backup));
  270. DrawerMenuUtil.removeMenuItem(menu, R.id.nav_synced_folders,
  271. getResources().getBoolean(R.bool.syncedFolder_light));
  272. DrawerMenuUtil.removeMenuItem(menu, R.id.nav_logout, !getResources().getBoolean(R.bool.show_drawer_logout));
  273. }
  274. @Subscribe(threadMode = ThreadMode.MAIN)
  275. public void onMessageEvent(DummyDrawerEvent event) {
  276. unsetAllDrawerMenuItems();
  277. }
  278. private void onNavigationItemClicked(final MenuItem menuItem) {
  279. setDrawerMenuItemChecked(menuItem.getItemId());
  280. switch (menuItem.getItemId()) {
  281. case R.id.nav_all_files:
  282. showFiles(false);
  283. if ((this instanceof FileDisplayActivity) &&
  284. !(((FileDisplayActivity) this).getListOfFilesFragment() instanceof PhotoFragment)) {
  285. ((FileDisplayActivity) this).browseToRoot();
  286. EventBus.getDefault().post(new ChangeMenuEvent());
  287. } else {
  288. Intent intent = new Intent(getApplicationContext(), FileDisplayActivity.class);
  289. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  290. intent.setAction(FileDisplayActivity.ALL_FILES);
  291. intent.putExtra(FileDisplayActivity.DRAWER_MENU_ID, menuItem.getItemId());
  292. startActivity(intent);
  293. }
  294. break;
  295. case R.id.nav_favorites:
  296. handleSearchEvents(new SearchEvent("", SearchRemoteOperation.SearchType.FAVORITE_SEARCH),
  297. menuItem.getItemId());
  298. break;
  299. case R.id.nav_photos:
  300. startPhotoSearch(menuItem);
  301. break;
  302. case R.id.nav_on_device:
  303. EventBus.getDefault().post(new ChangeMenuEvent());
  304. showFiles(true);
  305. break;
  306. case R.id.nav_uploads:
  307. startActivity(UploadListActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP);
  308. break;
  309. case R.id.nav_trashbin:
  310. startActivity(TrashbinActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP);
  311. break;
  312. case R.id.nav_activity:
  313. startActivity(ActivitiesActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP);
  314. break;
  315. case R.id.nav_notifications:
  316. startActivity(NotificationsActivity.class);
  317. break;
  318. case R.id.nav_synced_folders:
  319. startActivity(SyncedFoldersActivity.class);
  320. break;
  321. case R.id.nav_contacts:
  322. startActivity(ContactsPreferenceActivity.class);
  323. break;
  324. case R.id.nav_settings:
  325. startActivity(SettingsActivity.class);
  326. break;
  327. case R.id.nav_community:
  328. startActivity(CommunityActivity.class);
  329. break;
  330. case R.id.nav_logout:
  331. mCheckedMenuItem = -1;
  332. menuItem.setChecked(false);
  333. final Optional<User> optionalUser = getUser();
  334. if (optionalUser.isPresent()) {
  335. UserInfoActivity.openAccountRemovalConfirmationDialog(optionalUser.get(), getSupportFragmentManager());
  336. }
  337. break;
  338. case R.id.nav_shared:
  339. handleSearchEvents(new SearchEvent("", SearchRemoteOperation.SearchType.SHARED_FILTER),
  340. menuItem.getItemId());
  341. break;
  342. case R.id.nav_recently_modified:
  343. handleSearchEvents(new SearchEvent("", SearchRemoteOperation.SearchType.RECENTLY_MODIFIED_SEARCH),
  344. menuItem.getItemId());
  345. break;
  346. default:
  347. if (menuItem.getItemId() >= MENU_ITEM_EXTERNAL_LINK &&
  348. menuItem.getItemId() <= MENU_ITEM_EXTERNAL_LINK + 100) {
  349. // external link clicked
  350. externalLinkClicked(menuItem);
  351. } else {
  352. Log_OC.i(TAG, "Unknown drawer menu item clicked: " + menuItem.getTitle());
  353. }
  354. break;
  355. }
  356. }
  357. private void startActivity(Class<? extends Activity> activity) {
  358. startActivity(new Intent(getApplicationContext(), activity));
  359. }
  360. private void startActivity(Class<? extends Activity> activity, int flags) {
  361. Intent intent = new Intent(getApplicationContext(), activity);
  362. intent.setFlags(flags);
  363. startActivity(intent);
  364. }
  365. public void showManageAccountsDialog() {
  366. ChooseAccountDialogFragment choseAccountDialog = ChooseAccountDialogFragment.newInstance(accountManager.getUser());
  367. choseAccountDialog.show(getSupportFragmentManager(), "fragment_chose_account");
  368. }
  369. public void openManageAccounts() {
  370. Intent manageAccountsIntent = new Intent(getApplicationContext(), ManageAccountsActivity.class);
  371. startActivityForResult(manageAccountsIntent, ACTION_MANAGE_ACCOUNTS);
  372. }
  373. public void openAddAccount() {
  374. boolean isProviderOrOwnInstallationVisible = getResources()
  375. .getBoolean(R.bool.show_provider_or_own_installation);
  376. if (isProviderOrOwnInstallationVisible) {
  377. Intent firstRunIntent = new Intent(getApplicationContext(), FirstRunActivity.class);
  378. firstRunIntent.putExtra(FirstRunActivity.EXTRA_ALLOW_CLOSE, true);
  379. startActivity(firstRunIntent);
  380. } else {
  381. startAccountCreation();
  382. }
  383. }
  384. private void startPhotoSearch(MenuItem menuItem) {
  385. SearchEvent searchEvent = new SearchEvent("image/%", SearchRemoteOperation.SearchType.PHOTO_SEARCH);
  386. Intent intent = new Intent(getApplicationContext(), FileDisplayActivity.class);
  387. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  388. intent.setAction(Intent.ACTION_SEARCH);
  389. intent.putExtra(OCFileListFragment.SEARCH_EVENT, Parcels.wrap(searchEvent));
  390. intent.putExtra(FileDisplayActivity.DRAWER_MENU_ID, menuItem.getItemId());
  391. startActivity(intent);
  392. }
  393. private void handleSearchEvents(SearchEvent searchEvent, int menuItemId) {
  394. if (this instanceof FileDisplayActivity) {
  395. if (((FileDisplayActivity) this).getListOfFilesFragment() instanceof PhotoFragment) {
  396. Intent intent = new Intent(getApplicationContext(), FileDisplayActivity.class);
  397. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  398. intent.setAction(Intent.ACTION_SEARCH);
  399. intent.putExtra(OCFileListFragment.SEARCH_EVENT, Parcels.wrap(searchEvent));
  400. intent.putExtra(FileDisplayActivity.DRAWER_MENU_ID, menuItemId);
  401. startActivity(intent);
  402. } else {
  403. EventBus.getDefault().post(searchEvent);
  404. }
  405. } else {
  406. Intent intent = new Intent(getApplicationContext(), FileDisplayActivity.class);
  407. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  408. intent.setAction(Intent.ACTION_SEARCH);
  409. intent.putExtra(OCFileListFragment.SEARCH_EVENT, Parcels.wrap(searchEvent));
  410. intent.putExtra(FileDisplayActivity.DRAWER_MENU_ID, menuItemId);
  411. startActivity(intent);
  412. }
  413. }
  414. /**
  415. * show the file list to the user.
  416. *
  417. * @param onDeviceOnly flag to decide if all files or only the ones on the device should be shown
  418. */
  419. public abstract void showFiles(boolean onDeviceOnly);
  420. /**
  421. * sets the new/current account and restarts. In case the given account equals the actual/current account the call
  422. * will be ignored.
  423. *
  424. * @param hashCode HashCode of account to be set
  425. */
  426. public void accountClicked(int hashCode) {
  427. final User currentUser = accountManager.getUser();
  428. if (currentUser.hashCode() != hashCode && accountManager.setCurrentOwnCloudAccount(hashCode)) {
  429. fetchExternalLinks(true);
  430. restart();
  431. }
  432. }
  433. private void externalLinkClicked(MenuItem menuItem) {
  434. for (ExternalLink link : externalLinksProvider.getExternalLink(ExternalLinkType.LINK)) {
  435. if (menuItem.getTitle().toString().equalsIgnoreCase(link.name)) {
  436. if (link.redirect) {
  437. Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link.url));
  438. DisplayUtils.startIntentIfAppAvailable(intent, this, R.string.no_browser_available);
  439. } else {
  440. Intent externalWebViewIntent = new Intent(getApplicationContext(), ExternalSiteWebView.class);
  441. externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, link.name);
  442. externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_URL, link.url);
  443. externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, true);
  444. externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_MENU_ITEM_ID, menuItem.getItemId());
  445. startActivity(externalWebViewIntent);
  446. }
  447. }
  448. }
  449. }
  450. /**
  451. * checks if the drawer exists and is opened.
  452. *
  453. * @return <code>true</code> if the drawer is open, else <code>false</code>
  454. */
  455. public boolean isDrawerOpen() {
  456. return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START);
  457. }
  458. /**
  459. * closes the drawer.
  460. */
  461. public void closeDrawer() {
  462. if (mDrawerLayout != null) {
  463. mDrawerLayout.closeDrawer(GravityCompat.START);
  464. }
  465. }
  466. /**
  467. * opens the drawer.
  468. */
  469. public void openDrawer() {
  470. if (mDrawerLayout != null) {
  471. mDrawerLayout.openDrawer(GravityCompat.START);
  472. updateExternalLinksInDrawer();
  473. updateQuotaLink();
  474. }
  475. }
  476. /**
  477. * Enable or disable interaction with all drawers.
  478. *
  479. * @param lockMode The new lock mode for the given drawer. One of {@link DrawerLayout#LOCK_MODE_UNLOCKED}, {@link
  480. * DrawerLayout#LOCK_MODE_LOCKED_CLOSED} or {@link DrawerLayout#LOCK_MODE_LOCKED_OPEN}.
  481. */
  482. public void setDrawerLockMode(int lockMode) {
  483. if (mDrawerLayout != null) {
  484. mDrawerLayout.setDrawerLockMode(lockMode);
  485. }
  486. }
  487. /**
  488. * Enable or disable the drawer indicator.
  489. *
  490. * @param enable true to enable, false to disable
  491. */
  492. public void setDrawerIndicatorEnabled(boolean enable) {
  493. if (mDrawerToggle != null) {
  494. mDrawerToggle.setDrawerIndicatorEnabled(enable);
  495. }
  496. }
  497. /**
  498. * Updates title bar and home buttons (state and icon). Assumes that navigation drawer is NOT visible.
  499. */
  500. protected void updateActionBarTitleAndHomeButton(OCFile chosenFile) {
  501. super.updateActionBarTitleAndHomeButton(chosenFile);
  502. // set home button properties
  503. if (mDrawerToggle != null) {
  504. if (chosenFile != null && isRoot(chosenFile)) {
  505. mDrawerToggle.setDrawerIndicatorEnabled(true);
  506. } else {
  507. mDrawerToggle.setDrawerIndicatorEnabled(false);
  508. }
  509. }
  510. }
  511. /**
  512. * shows or hides the quota UI elements.
  513. *
  514. * @param showQuota show/hide quota information
  515. */
  516. private void showQuota(boolean showQuota) {
  517. if (showQuota) {
  518. mQuotaView.setVisibility(View.VISIBLE);
  519. } else {
  520. mQuotaView.setVisibility(View.GONE);
  521. }
  522. }
  523. /**
  524. * configured the quota to be displayed.
  525. *
  526. * @param usedSpace the used space
  527. * @param totalSpace the total space
  528. * @param relative the percentage of space already used
  529. * @param quotaValue {@link GetUserInfoRemoteOperation#SPACE_UNLIMITED} or other to determinate state
  530. */
  531. private void setQuotaInformation(long usedSpace, long totalSpace, int relative, long quotaValue) {
  532. if (GetUserInfoRemoteOperation.SPACE_UNLIMITED == quotaValue) {
  533. mQuotaTextPercentage.setText(String.format(
  534. getString(R.string.drawer_quota_unlimited),
  535. DisplayUtils.bytesToHumanReadable(usedSpace)));
  536. } else {
  537. mQuotaTextPercentage.setText(String.format(
  538. getString(R.string.drawer_quota),
  539. DisplayUtils.bytesToHumanReadable(usedSpace),
  540. DisplayUtils.bytesToHumanReadable(totalSpace)));
  541. }
  542. mQuotaProgressBar.setProgress(relative);
  543. ThemeUtils.colorProgressBar(mQuotaProgressBar, DisplayUtils.getRelativeInfoColor(this, relative));
  544. updateQuotaLink();
  545. showQuota(true);
  546. }
  547. private void unsetAllDrawerMenuItems() {
  548. if (mNavigationView != null) {
  549. mNavigationView.getMenu();
  550. Menu menu = mNavigationView.getMenu();
  551. for (int i = 0; i < menu.size(); i++) {
  552. menu.getItem(i).setChecked(false);
  553. }
  554. }
  555. mCheckedMenuItem = Menu.NONE;
  556. }
  557. private void updateQuotaLink() {
  558. if (mQuotaTextLink != null) {
  559. if (getBaseContext().getResources().getBoolean(R.bool.show_external_links)) {
  560. List<ExternalLink> quotas = externalLinksProvider.getExternalLink(ExternalLinkType.QUOTA);
  561. float density = getResources().getDisplayMetrics().density;
  562. final int size = Math.round(24 * density);
  563. if (quotas.size() > 0) {
  564. final ExternalLink firstQuota = quotas.get(0);
  565. mQuotaTextLink.setText(firstQuota.name);
  566. mQuotaTextLink.setClickable(true);
  567. mQuotaTextLink.setVisibility(View.VISIBLE);
  568. mQuotaTextLink.setOnClickListener(v -> {
  569. Intent externalWebViewIntent = new Intent(getApplicationContext(), ExternalSiteWebView.class);
  570. externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, firstQuota.name);
  571. externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_URL, firstQuota.url);
  572. externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, true);
  573. externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_MENU_ITEM_ID, -1);
  574. startActivity(externalWebViewIntent);
  575. });
  576. SimpleTarget target = new SimpleTarget<Drawable>() {
  577. @Override
  578. public void onResourceReady(Drawable resource, GlideAnimation glideAnimation) {
  579. Drawable test = resource.getCurrent();
  580. test.setBounds(0, 0, size, size);
  581. mQuotaTextLink.setCompoundDrawablesWithIntrinsicBounds(test, null, null, null);
  582. }
  583. @Override
  584. public void onLoadFailed(Exception e, Drawable errorDrawable) {
  585. super.onLoadFailed(e, errorDrawable);
  586. Drawable test = errorDrawable.getCurrent();
  587. test.setBounds(0, 0, size, size);
  588. mQuotaTextLink.setCompoundDrawablesWithIntrinsicBounds(test, null, null, null);
  589. }
  590. };
  591. DisplayUtils.downloadIcon(getUserAccountManager(),
  592. clientFactory,
  593. this,
  594. firstQuota.iconUrl,
  595. target,
  596. R.drawable.ic_link,
  597. size,
  598. size);
  599. } else {
  600. mQuotaTextLink.setVisibility(View.GONE);
  601. }
  602. } else {
  603. mQuotaTextLink.setVisibility(View.GONE);
  604. }
  605. }
  606. }
  607. /**
  608. * checks/highlights the provided menu item if the drawer has been initialized and the menu item exists.
  609. *
  610. * @param menuItemId the menu item to be highlighted
  611. */
  612. protected void setDrawerMenuItemChecked(int menuItemId) {
  613. if (mNavigationView != null && mNavigationView.getMenu().findItem(menuItemId) != null) {
  614. mCheckedMenuItem = menuItemId;
  615. MenuItem currentItem = mNavigationView.getMenu().findItem(menuItemId);
  616. int drawerColor = getResources().getColor(R.color.drawer_text_color);
  617. int activeColor = ThemeUtils.elementColor(this);
  618. currentItem.setChecked(true);
  619. // For each menu item, change the color of the selected item, and of the other items
  620. for (int i = 0; i < mNavigationView.getMenu().size(); i++) {
  621. MenuItem menuItem = mNavigationView.getMenu().getItem(i);
  622. if (menuItem.getIcon() != null) {
  623. if (menuItem != currentItem) {
  624. ThemeUtils.tintDrawable(menuItem.getIcon(), drawerColor);
  625. ThemeUtils.tintMenuItemText(menuItem, drawerColor);
  626. } else {
  627. ThemeUtils.tintDrawable(currentItem.getIcon(), activeColor);
  628. ThemeUtils.tintMenuItemText(currentItem, activeColor);
  629. }
  630. }
  631. }
  632. } else {
  633. Log_OC.w(TAG, "setDrawerMenuItemChecked has been called with invalid menu-item-ID");
  634. }
  635. }
  636. /**
  637. * Retrieves and shows the user quota if available
  638. */
  639. private void getAndDisplayUserQuota() {
  640. // set user space information
  641. Thread t = new Thread(() -> {
  642. final User user = accountManager.getUser();
  643. if (user.isAnonymous()) {
  644. return;
  645. }
  646. final Context context = MainApp.getAppContext();
  647. RemoteOperationResult result = new GetUserInfoRemoteOperation().execute(user.toPlatformAccount(), context);
  648. if (result.isSuccess() && result.getData() != null) {
  649. final UserInfo userInfo = (UserInfo) result.getData().get(0);
  650. final Quota quota = userInfo.getQuota();
  651. if (quota != null) {
  652. final long used = quota.getUsed();
  653. final long total = quota.getTotal();
  654. final int relative = (int) Math.ceil(quota.getRelative());
  655. final long quotaValue = quota.getQuota();
  656. runOnUiThread(() -> {
  657. if (quotaValue > 0 || quotaValue == GetUserInfoRemoteOperation.SPACE_UNLIMITED
  658. || quotaValue == GetUserInfoRemoteOperation.QUOTA_LIMIT_INFO_NOT_AVAILABLE) {
  659. /*
  660. * show quota in case
  661. * it is available and calculated (> 0) or
  662. * in case of legacy servers (==QUOTA_LIMIT_INFO_NOT_AVAILABLE)
  663. */
  664. setQuotaInformation(used, total, relative, quotaValue);
  665. } else {
  666. /*
  667. * quotaValue < 0 means special cases like
  668. * {@link RemoteGetUserQuotaOperation.SPACE_NOT_COMPUTED},
  669. * {@link RemoteGetUserQuotaOperation.SPACE_UNKNOWN} or
  670. * {@link RemoteGetUserQuotaOperation.SPACE_UNLIMITED}
  671. * thus don't display any quota information.
  672. */
  673. showQuota(false);
  674. }
  675. });
  676. }
  677. }
  678. });
  679. t.start();
  680. }
  681. private void updateExternalLinksInDrawer() {
  682. if (mNavigationView != null && getBaseContext().getResources().getBoolean(R.bool.show_external_links)) {
  683. mNavigationView.getMenu().removeGroup(R.id.drawer_menu_external_links);
  684. float density = getResources().getDisplayMetrics().density;
  685. final int size = Math.round(24 * density);
  686. int greyColor = ContextCompat.getColor(this, R.color.drawer_menu_icon);
  687. for (final ExternalLink link : externalLinksProvider.getExternalLink(ExternalLinkType.LINK)) {
  688. int id = mNavigationView.getMenu().add(R.id.drawer_menu_external_links,
  689. MENU_ITEM_EXTERNAL_LINK + link.id, MENU_ORDER_EXTERNAL_LINKS, link.name)
  690. .setCheckable(true).getItemId();
  691. MenuSimpleTarget target = new MenuSimpleTarget<Drawable>(id) {
  692. @Override
  693. public void onResourceReady(Drawable resource, GlideAnimation glideAnimation) {
  694. setExternalLinkIcon(getIdMenuItem(), resource, greyColor);
  695. }
  696. @Override
  697. public void onLoadFailed(Exception e, Drawable errorDrawable) {
  698. super.onLoadFailed(e, errorDrawable);
  699. setExternalLinkIcon(getIdMenuItem(), errorDrawable, greyColor);
  700. }
  701. };
  702. DisplayUtils.downloadIcon(getUserAccountManager(),
  703. clientFactory,
  704. this,
  705. link.iconUrl,
  706. target,
  707. R.drawable.ic_link,
  708. size,
  709. size);
  710. }
  711. setDrawerMenuItemChecked(mCheckedMenuItem);
  712. }
  713. }
  714. private void setExternalLinkIcon(int id, Drawable drawable, int greyColor) {
  715. MenuItem menuItem = mNavigationView.getMenu().findItem(id);
  716. if (menuItem != null) {
  717. if (drawable != null) {
  718. menuItem.setIcon(ThemeUtils.tintDrawable(drawable, greyColor));
  719. } else {
  720. menuItem.setIcon(R.drawable.ic_link);
  721. }
  722. }
  723. }
  724. @Override
  725. protected void onCreate(Bundle savedInstanceState) {
  726. super.onCreate(savedInstanceState);
  727. if (savedInstanceState != null) {
  728. mIsAccountChooserActive = savedInstanceState.getBoolean(KEY_IS_ACCOUNT_CHOOSER_ACTIVE, false);
  729. mCheckedMenuItem = savedInstanceState.getInt(KEY_CHECKED_MENU_ITEM, Menu.NONE);
  730. }
  731. externalLinksProvider = new ExternalLinksProvider(getContentResolver());
  732. arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver());
  733. }
  734. @Override
  735. protected void onSaveInstanceState(@NonNull Bundle outState) {
  736. super.onSaveInstanceState(outState);
  737. outState.putBoolean(KEY_IS_ACCOUNT_CHOOSER_ACTIVE, mIsAccountChooserActive);
  738. outState.putInt(KEY_CHECKED_MENU_ITEM, mCheckedMenuItem);
  739. }
  740. @Override
  741. public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
  742. super.onRestoreInstanceState(savedInstanceState);
  743. mIsAccountChooserActive = savedInstanceState.getBoolean(KEY_IS_ACCOUNT_CHOOSER_ACTIVE, false);
  744. mCheckedMenuItem = savedInstanceState.getInt(KEY_CHECKED_MENU_ITEM, Menu.NONE);
  745. // check/highlight the menu item if present
  746. if (mCheckedMenuItem > Menu.NONE || mCheckedMenuItem < Menu.NONE) {
  747. setDrawerMenuItemChecked(mCheckedMenuItem);
  748. }
  749. }
  750. @Override
  751. protected void onPostCreate(Bundle savedInstanceState) {
  752. super.onPostCreate(savedInstanceState);
  753. // Sync the toggle state after onRestoreInstanceState has occurred.
  754. if (mDrawerToggle != null) {
  755. mDrawerToggle.syncState();
  756. if (isDrawerOpen()) {
  757. mDrawerToggle.setDrawerIndicatorEnabled(true);
  758. }
  759. }
  760. updateExternalLinksInDrawer();
  761. updateQuotaLink();
  762. }
  763. @Override
  764. public void onConfigurationChanged(@NonNull Configuration newConfig) {
  765. super.onConfigurationChanged(newConfig);
  766. if (mDrawerToggle != null) {
  767. mDrawerToggle.onConfigurationChanged(newConfig);
  768. }
  769. }
  770. @Override
  771. public void onBackPressed() {
  772. if (isDrawerOpen()) {
  773. closeDrawer();
  774. return;
  775. }
  776. super.onBackPressed();
  777. }
  778. @Override
  779. protected void onResume() {
  780. super.onResume();
  781. if (AppCompatDelegate.getDefaultNightMode() != AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) {
  782. getDelegate().setLocalNightMode(DarkMode.DARK == preferences.getDarkThemeMode() ?
  783. AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
  784. getDelegate().applyDayNight();
  785. }
  786. setDrawerMenuItemChecked(mCheckedMenuItem);
  787. }
  788. @Override
  789. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  790. super.onActivityResult(requestCode, resultCode, data);
  791. // update Account list and active account if Manage Account activity replies with
  792. // - ACCOUNT_LIST_CHANGED = true
  793. // - RESULT_OK
  794. if (requestCode == ACTION_MANAGE_ACCOUNTS && resultCode == RESULT_OK
  795. && data.getBooleanExtra(ManageAccountsActivity.KEY_ACCOUNT_LIST_CHANGED, false)) {
  796. // current account has changed
  797. if (data.getBooleanExtra(ManageAccountsActivity.KEY_CURRENT_ACCOUNT_CHANGED, false)) {
  798. setAccount(accountManager.getCurrentAccount(), false);
  799. restart();
  800. }
  801. } else if (requestCode == PassCodeManager.PASSCODE_ACTIVITY &&
  802. Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && data != null) {
  803. int result = data.getIntExtra(RequestCredentialsActivity.KEY_CHECK_RESULT,
  804. RequestCredentialsActivity.KEY_CHECK_RESULT_FALSE);
  805. if (result == RequestCredentialsActivity.KEY_CHECK_RESULT_CANCEL) {
  806. Log_OC.d(TAG, "PassCodeManager cancelled");
  807. preferences.setLockTimestamp(0);
  808. finish();
  809. }
  810. }
  811. }
  812. /**
  813. * Quota view can be either at navigation bottom or header
  814. *
  815. * @param id the view's id
  816. * @return The view if found or <code>null</code> otherwise.
  817. */
  818. private View findQuotaViewById(int id) {
  819. View v = ((NavigationView) findViewById(R.id.nav_view)).getHeaderView(0).findViewById(id);
  820. if (v != null) {
  821. return v;
  822. } else {
  823. return findViewById(id);
  824. }
  825. }
  826. /**
  827. * restart helper method which is called after a changing the current account.
  828. */
  829. protected abstract void restart();
  830. @Override
  831. public void avatarGenerated(Drawable avatarDrawable, Object callContext) {
  832. if (callContext instanceof MenuItem) {
  833. MenuItem menuItem = (MenuItem) callContext;
  834. menuItem.setIcon(avatarDrawable);
  835. } else if (callContext instanceof ImageView) {
  836. ImageView imageView = (ImageView) callContext;
  837. imageView.setImageDrawable(avatarDrawable);
  838. } else if (callContext instanceof MaterialButton) {
  839. MaterialButton materialButton = (MaterialButton) callContext;
  840. materialButton.setIcon(avatarDrawable);
  841. }
  842. }
  843. @Override
  844. public boolean shouldCallGeneratedCallback(String tag, Object callContext) {
  845. if (callContext instanceof MenuItem) {
  846. MenuItem menuItem = (MenuItem) callContext;
  847. return String.valueOf(menuItem.getTitle()).equals(tag);
  848. } else if (callContext instanceof ImageView) {
  849. ImageView imageView = (ImageView) callContext;
  850. return String.valueOf(imageView.getTag()).equals(tag);
  851. } else if (callContext instanceof MaterialButton) {
  852. MaterialButton materialButton = (MaterialButton) callContext;
  853. return String.valueOf(materialButton.getTag()).equals(tag);
  854. }
  855. return false;
  856. }
  857. /**
  858. * Adds other listeners to react on changes of the drawer layout.
  859. *
  860. * @param listener Object interested in changes of the drawer layout.
  861. */
  862. public void addDrawerListener(DrawerLayout.DrawerListener listener) {
  863. if (mDrawerLayout != null) {
  864. mDrawerLayout.addDrawerListener(listener);
  865. } else {
  866. Log_OC.e(TAG, "Drawer layout not ready to add drawer listener");
  867. }
  868. }
  869. public boolean isDrawerIndicatorAvailable() {
  870. return true;
  871. }
  872. @Override
  873. protected void onStart() {
  874. super.onStart();
  875. EventBus.getDefault().register(this);
  876. }
  877. @Override
  878. protected void onStop() {
  879. if (preferences.getLockTimestamp() != 0) {
  880. preferences.setLockTimestamp(SystemClock.elapsedRealtime());
  881. }
  882. EventBus.getDefault().unregister(this);
  883. super.onStop();
  884. }
  885. @Subscribe(threadMode = ThreadMode.MAIN)
  886. public void onAccountRemovedEvent(AccountRemovedEvent event) {
  887. restart();
  888. }
  889. /**
  890. * Retrieves external links via api from 'external' app
  891. */
  892. public void fetchExternalLinks(final boolean force) {
  893. if (getBaseContext().getResources().getBoolean(R.bool.show_external_links)) {
  894. Thread t = new Thread(() -> {
  895. // fetch capabilities as early as possible
  896. if ((getCapabilities() == null || getCapabilities().getAccountName().isEmpty())
  897. && getStorageManager() != null) {
  898. GetCapabilitiesOperation getCapabilities = new GetCapabilitiesOperation();
  899. getCapabilities.execute(getStorageManager(), getBaseContext());
  900. }
  901. User user = accountManager.getUser();
  902. String name = user.getAccountName();
  903. if (getStorageManager() != null && getStorageManager().getCapability(name).getExternalLinks().isTrue()) {
  904. int count = arbitraryDataProvider.getIntegerValue(FilesSyncHelper.GLOBAL,
  905. FileActivity.APP_OPENED_COUNT);
  906. if (count > 10 || count == -1 || force) {
  907. if (force) {
  908. Log_OC.d("ExternalLinks", "force update");
  909. }
  910. arbitraryDataProvider.storeOrUpdateKeyValue(FilesSyncHelper.GLOBAL,
  911. FileActivity.APP_OPENED_COUNT, "0");
  912. Log_OC.d("ExternalLinks", "update via api");
  913. RemoteOperation getExternalLinksOperation = new ExternalLinksOperation();
  914. RemoteOperationResult result = getExternalLinksOperation.execute(user.toPlatformAccount(), this);
  915. if (result.isSuccess() && result.getData() != null) {
  916. externalLinksProvider.deleteAllExternalLinks();
  917. ArrayList<ExternalLink> externalLinks = (ArrayList<ExternalLink>) (Object) result.getData();
  918. for (ExternalLink link : externalLinks) {
  919. externalLinksProvider.storeExternalLink(link);
  920. }
  921. }
  922. } else {
  923. arbitraryDataProvider.storeOrUpdateKeyValue(FilesSyncHelper.GLOBAL,
  924. FileActivity.APP_OPENED_COUNT, String.valueOf(count + 1));
  925. }
  926. } else {
  927. externalLinksProvider.deleteAllExternalLinks();
  928. Log_OC.d("ExternalLinks", "links disabled");
  929. }
  930. runOnUiThread(this::updateExternalLinksInDrawer);
  931. });
  932. t.start();
  933. }
  934. }
  935. }