DrawerActivity.java 42 KB

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