diff --git a/build.gradle b/build.gradle index 6e80c65cc5c8..098dc8ed7592 100644 --- a/build.gradle +++ b/build.gradle @@ -33,7 +33,7 @@ dependencies { /// dependencies for app building compile name: 'touch-image-view' - compile 'com.github.nextcloud:android-library:1.0.2' + compile 'com.github.nextcloud:android-library:1.0.6' compile "com.android.support:support-v4:${supportLibraryVersion}" compile "com.android.support:design:${supportLibraryVersion}" compile 'com.jakewharton:disklrucache:2.0.2' diff --git a/res/layout/drawer.xml b/res/layout/drawer.xml index 843a1ebdf3df..cca0b25ba328 100644 --- a/res/layout/drawer.xml +++ b/res/layout/drawer.xml @@ -24,9 +24,43 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" + android:layout_weight="1" android:fitsSystemWindows="true" - app:theme="@style/NavigationView_ItemTextAppearance" app:headerLayout="@layout/drawer_header" - app:menu="@menu/drawer_menu"/> + app:menu="@menu/drawer_menu" + app:theme="@style/NavigationView_ItemTextAppearance"> + + + + + + + + + \ No newline at end of file diff --git a/res/menu/drawer_menu.xml b/res/menu/drawer_menu.xml index 9551b98274dd..7c6462f8e8c6 100644 --- a/res/menu/drawer_menu.xml +++ b/res/menu/drawer_menu.xml @@ -67,4 +67,15 @@ android:icon="@drawable/ic_settings" android:title="@string/actionbar_settings"/> + + + + + \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml index 201060c8c6f9..3252a60f75dc 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -52,4 +52,10 @@ #201D2D44 #40162233 + + + @color/color_accent + #fdd835 + #e57373 + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 10b3b8aa4e67..259a93884dd5 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -23,6 +23,7 @@ On device Settings Uploads + %1$s of %2$s used Close Open General diff --git a/src/com/owncloud/android/ui/activity/DrawerActivity.java b/src/com/owncloud/android/ui/activity/DrawerActivity.java index bab0cde0a382..7b81013aad2a 100644 --- a/src/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/src/com/owncloud/android/ui/activity/DrawerActivity.java @@ -38,6 +38,8 @@ import android.view.MenuItem; import android.view.View; import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; import android.widget.TextView; import com.owncloud.android.MainApp; @@ -45,7 +47,10 @@ import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.lib.common.OwnCloudAccount; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.lib.resources.users.RemoteGetUserQuotaOperation; import com.owncloud.android.ui.TextDrawable; import com.owncloud.android.utils.DisplayUtils; @@ -121,6 +126,21 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU */ private Account[] mAvatars = new Account[3]; + /** + * container layout of the quota view. + */ + private LinearLayout mQuotaView; + + /** + * progress bar of the quota view. + */ + private ProgressBar mQuotaProgressBar; + + /** + * text view of the quota view. + */ + private TextView mQuotaTextView; + /** * Initializes the drawer, its content and highlights the menu item with the given id. * This method needs to be called after the content view has been set. @@ -141,30 +161,22 @@ protected void setupDrawer() { mNavigationView = (NavigationView) findViewById(R.id.nav_view); if (mNavigationView != null) { - mAccountChooserToggle = (ImageView) findNavigationViewChildById(R.id.drawer_account_chooser_toogle); - mAccountChooserToggle.setImageResource(R.drawable.ic_down); - mIsAccountChooserActive = false; + setupDrawerHeader(); - mAccountMiddleAccountAvatar = (ImageView) findNavigationViewChildById(R.id.drawer_account_middle); - mAccountEndAccountAvatar = (ImageView) findNavigationViewChildById(R.id.drawer_account_end); + setupDrawerMenu(mNavigationView); - // on pre lollipop the light theme adds a black tint to icons with white coloring - // ruining the generic avatars, so tinting for icons is deactivated pre lollipop - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - mNavigationView.setItemIconTintList(null); - } + setupQuotaElement(); + } - setupDrawerContent(mNavigationView); + setupDrawerToggle(); - findNavigationViewChildById(R.id.drawer_active_user) - .setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - toggleAccountList(); - } - }); - } + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + /** + * initializes and sets up the drawer toggle. + */ + private void setupDrawerToggle() { mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.drawer_open, R.string.drawer_close) { /** Called when a drawer has settled in a completely closed state. */ @@ -188,7 +200,35 @@ public void onDrawerOpened(View drawerView) { // Set the drawer toggle as the DrawerListener mDrawerLayout.setDrawerListener(mDrawerToggle); mDrawerToggle.setDrawerIndicatorEnabled(true); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + /** + * initializes and sets up the drawer header. + */ + private void setupDrawerHeader() { + mAccountChooserToggle = (ImageView) findNavigationViewChildById(R.id.drawer_account_chooser_toogle); + mAccountChooserToggle.setImageResource(R.drawable.ic_down); + mIsAccountChooserActive = false; + mAccountMiddleAccountAvatar = (ImageView) findNavigationViewChildById(R.id.drawer_account_middle); + mAccountEndAccountAvatar = (ImageView) findNavigationViewChildById(R.id.drawer_account_end); + + findNavigationViewChildById(R.id.drawer_active_user) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + toggleAccountList(); + } + }); + } + + /** + * setup quota elements of the drawer. + */ + private void setupQuotaElement() { + mQuotaView = (LinearLayout) findViewById(R.id.drawer_quota); + mQuotaProgressBar = (ProgressBar) findViewById(R.id.drawer_quota_ProgressBar); + mQuotaTextView = (TextView) findViewById(R.id.drawer_quota_text); + DisplayUtils.colorPreLollipopHorizontalProgressBar(mQuotaProgressBar); } /** @@ -196,7 +236,14 @@ public void onDrawerOpened(View drawerView) { * * @param navigationView the drawers navigation view */ - protected void setupDrawerContent(NavigationView navigationView) { + protected void setupDrawerMenu(NavigationView navigationView) { + // on pre lollipop the light theme adds a black tint to icons with white coloring + // ruining the generic avatars, so tinting for icons is deactivated pre lollipop + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + navigationView.setItemIconTintList(null); + } + + // setup actions for drawer menu items navigationView.setNavigationItemSelectedListener( new NavigationView.OnNavigationItemSelectedListener() { @Override @@ -246,9 +293,9 @@ public boolean onNavigationItemSelected(MenuItem menuItem) { // handle correct state if (mIsAccountChooserActive) { - mNavigationView.getMenu().setGroupVisible(R.id.drawer_menu_accounts, true); + navigationView.getMenu().setGroupVisible(R.id.drawer_menu_accounts, true); } else { - mNavigationView.getMenu().setGroupVisible(R.id.drawer_menu_accounts, false); + navigationView.getMenu().setGroupVisible(R.id.drawer_menu_accounts, false); } } @@ -449,6 +496,9 @@ protected void setAccountInDrawer(Account account) { DisplayUtils.setAvatar(account, this, mCurrentAccountAvatarRadiusDimension, getResources(), getStorageManager(), findNavigationViewChildById(R.id.drawer_current_account)); + + // check and show quota info if available + getAndDisplayUserQuota(); } } @@ -477,6 +527,38 @@ private void showMenu() { } } + /** + * shows or hides the quota UI elements. + * + * @param showQuota show/hide quota information + */ + private void showQuota(boolean showQuota) { + if (showQuota) { + mQuotaView.setVisibility(View.VISIBLE); + } else { + mQuotaView.setVisibility(View.GONE); + } + } + + /** + * configured the quota to be displayed. + * + * @param usedSpace the used space + * @param totalSpace the total space + * @param relative the percentage of space already used + */ + private void setQuotaInformation(long usedSpace, long totalSpace, int relative) { + mQuotaProgressBar.setProgress(relative); + DisplayUtils.colorHorizontalProgressBar(mQuotaProgressBar, DisplayUtils.getRelativeInfoColor(this, relative)); + + mQuotaTextView.setText(String.format( + getString(R.string.drawer_quota), + DisplayUtils.bytesToHumanReadable(usedSpace), + DisplayUtils.bytesToHumanReadable(totalSpace))); + + showQuota(true); + } + /** * checks/highlights the provided menu item if the drawer has been initialized and the menu item exists. * @@ -492,6 +574,57 @@ protected void setDrawerMenuItemChecked(int menuItemId) { } } + /** + * Retrieves and shows the user quota if available + */ + private void getAndDisplayUserQuota() { + // set user space information + Thread t = new Thread(new Runnable() { + public void run() { + + RemoteOperation getQuotaInfoOperation = new RemoteGetUserQuotaOperation(); + RemoteOperationResult result = getQuotaInfoOperation.execute( + AccountUtils.getCurrentOwnCloudAccount(DrawerActivity.this), DrawerActivity.this); + + if (result.isSuccess() && result.getData() != null) { + final RemoteGetUserQuotaOperation.Quota quota = + (RemoteGetUserQuotaOperation.Quota) result.getData().get(0); + + final long used = quota.getUsed(); + final long total = quota.getTotal(); + final int relative = (int) Math.ceil(quota.getRelative()); + final long quotaValue = quota.getQuota(); + + runOnUiThread(new Runnable() { + @Override + public void run() { + if (quotaValue > 0 + || quotaValue == RemoteGetUserQuotaOperation.QUOTA_LIMIT_INFO_NOT_AVAILABLE) { + /** + * show quota in case + * it is available and calculated (> 0) or + * in case of legacy servers (==QUOTA_LIMIT_INFO_NOT_AVAILABLE) + */ + setQuotaInformation(used, total, relative); + } else { + /** + * quotaValue < 0 means special cases like + * {@link RemoteGetUserQuotaOperation.SPACE_NOT_COMPUTED}, + * {@link RemoteGetUserQuotaOperation.SPACE_UNKNOWN} or + * {@link RemoteGetUserQuotaOperation.SPACE_UNLIMITED} + * thus don't display any quota information. + */ + showQuota(false); + } + } + }); + } + } + }); + + t.start(); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/src/com/owncloud/android/utils/DisplayUtils.java b/src/com/owncloud/android/utils/DisplayUtils.java index c9a81a9a614e..6ad06093fd7c 100644 --- a/src/com/owncloud/android/utils/DisplayUtils.java +++ b/src/com/owncloud/android/utils/DisplayUtils.java @@ -1,23 +1,25 @@ /** - * ownCloud Android client application + * Nextcloud Android client application * + * @author Andy Scherzinger * @author Bartek Przybylski * @author David A. Velasco * Copyright (C) 2011 Bartek Przybylski * Copyright (C) 2015 ownCloud Inc. + * Copyright (C) 2016 Andy Scherzinger * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see . */ package com.owncloud.android.utils; @@ -37,7 +39,6 @@ import android.support.v4.app.FragmentActivity; import android.support.v4.content.ContextCompat; import android.text.format.DateUtils; -import android.view.Display; import android.view.View; import android.widget.ProgressBar; import android.widget.SeekBar; @@ -60,20 +61,21 @@ import java.util.Map; /** - * A helper class for some string operations. + * A helper class for UI/display related operations. */ public class DisplayUtils { private static final String TAG = DisplayUtils.class.getSimpleName(); - - private static final String OWNCLOUD_APP_NAME = "ownCloud"; - + private static final String[] sizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; private static final int[] sizeScales = { 0, 0, 1, 1, 1, 2, 2, 2, 2 }; + public static final int RELATIVE_THRESHOLD_WARNING = 90; + public static final int RELATIVE_THRESHOLD_CRITICAL = 95; + public static final String MIME_TYPE_UNKNOWN = "Unknown type"; private static Map mimeType2HumanReadable; static { - mimeType2HumanReadable = new HashMap(); + mimeType2HumanReadable = new HashMap<>(); // images mimeType2HumanReadable.put("image/jpeg", "JPEG image"); mimeType2HumanReadable.put("image/jpg", "JPEG image"); @@ -94,19 +96,19 @@ public class DisplayUtils { *
  • rounds the size based on the suffix to 0,1 or 2 decimals
  • * * - * @param bytes Input file size - * @return Like something readable like "12 MB" + * @param bytes Input file size + * @return something readable like "12 MB" */ public static String bytesToHumanReadable(long bytes) { double result = bytes; - int attachedSuff = 0; - while (result > 1024 && attachedSuff < sizeSuffixes.length) { + int suffixIndex = 0; + while (result > 1024 && suffixIndex < sizeSuffixes.length) { result /= 1024.; - attachedSuff++; + suffixIndex++; } return new BigDecimal(result).setScale( - sizeScales[attachedSuff], BigDecimal.ROUND_HALF_UP) + " " + sizeSuffixes[attachedSuff]; + sizeScales[suffixIndex], BigDecimal.ROUND_HALF_UP) + " " + sizeSuffixes[suffixIndex]; } /** @@ -114,7 +116,7 @@ public static String bytesToHumanReadable(long bytes) { * like "JPG image". * * @param mimetype MIME type to convert - * @return A human friendly version of the MIME type + * @return A human friendly version of the MIME type, {@link #MIME_TYPE_UNKNOWN} if it can't be converted */ public static String convertMIMEtoPrettyPrint(String mimetype) { if (mimeType2HumanReadable.containsKey(mimetype)) { @@ -122,11 +124,12 @@ public static String convertMIMEtoPrettyPrint(String mimetype) { } if (mimetype.split("/").length >= 2) return mimetype.split("/")[1].toUpperCase() + " file"; - return "Unknown type"; + return MIME_TYPE_UNKNOWN; } /** * Converts Unix time to human readable format + * * @param milliseconds that have passed since 01/01/1970 * @return The human readable time for the users locale */ @@ -138,6 +141,7 @@ public static String unixTimeToHumanReadable(long milliseconds) { /** * Converts an internationalized domain name (IDN) in an URL to and from ASCII/Unicode. + * * @param url the URL where the domain name should be converted * @param toASCII if true converts from Unicode to ASCII, if false converts from ASCII to Unicode * @return the URL containing the converted domain name @@ -155,9 +159,9 @@ public static String convertIdn(String url, boolean toASCII) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { // Find host name after '//' or '@' int hostStart = 0; - if (urlNoDots.indexOf("//") != -1) { + if (urlNoDots.contains("//")) { hostStart = url.indexOf("//") + "//".length(); - } else if (url.indexOf("@") != -1) { + } else if (url.contains("@")) { hostStart = url.indexOf("@") + "@".length(); } @@ -207,17 +211,33 @@ public static CharSequence getRelativeTimestamp(Context context, long modificati DateUtils.WEEK_IN_MILLIS, 0); } - @SuppressWarnings("deprecation") - public static CharSequence getRelativeDateTimeString ( - Context c, long time, long minResolution, long transitionResolution, int flags - ){ - + /** + * determines the info level color based on certain thresholds + * {@link #RELATIVE_THRESHOLD_WARNING} and {@link #RELATIVE_THRESHOLD_CRITICAL}. + * + * @param context the app's context + * @param relative relative value for which the info level color should be looked up + * @return info level color + */ + public static int getRelativeInfoColor(Context context, int relative) { + if (relative < RELATIVE_THRESHOLD_WARNING) { + return context.getResources().getColor(R.color.infolevel_info); + } else if (relative >= RELATIVE_THRESHOLD_WARNING && relative < RELATIVE_THRESHOLD_CRITICAL) { + return context.getResources().getColor(R.color.infolevel_warning); + } else { + return context.getResources().getColor(R.color.infolevel_critical); + } + } + + public static CharSequence getRelativeDateTimeString( + Context c, long time, long minResolution, long transitionResolution, int flags) { + CharSequence dateString = ""; - + // in Future - if (time > System.currentTimeMillis()){ + if (time > System.currentTimeMillis()) { return DisplayUtils.unixTimeToHumanReadable(time); - } + } // < 60 seconds -> seconds ago else if ((System.currentTimeMillis() - time) < 60 * 1000) { return c.getString(R.string.file_list_seconds_ago); @@ -238,8 +258,9 @@ else if ((System.currentTimeMillis() - time) < 60 * 1000) { } /** - * Update the passed path removing the last "/" if it is not the root folder - * @param path + * Update the passed path removing the last "/" if it is not the root folder. + * + * @param path the path to be trimmed */ public static String getPathWithoutLastSlash(String path) { @@ -250,22 +271,16 @@ public static String getPathWithoutLastSlash(String path) { return path; } - /** - * Gets the screen size in pixels in a backwards compatible way + * Gets the screen size in pixels. * - * @param caller Activity calling; needed to get access to the {@link android.view.WindowManager} - * @return Size in pixels of the screen, or default {@link Point} if caller is null + * @param caller Activity calling; needed to get access to the {@link android.view.WindowManager} + * @return Size in pixels of the screen, or default {@link Point} if caller is null */ public static Point getScreenSize(Activity caller) { Point size = new Point(); if (caller != null) { - Display display = caller.getWindowManager().getDefaultDisplay(); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2) { - display.getSize(size); - } else { - size.set(display.getWidth(), display.getHeight()); - } + caller.getWindowManager().getDefaultDisplay().getSize(size); } return size; } @@ -277,7 +292,18 @@ public static Point getScreenSize(Activity caller) { */ public static void colorPreLollipopHorizontalProgressBar(ProgressBar progressBar) { if (progressBar != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - int color = progressBar.getResources().getColor(R.color.color_accent); + colorHorizontalProgressBar(progressBar, progressBar.getResources().getColor(R.color.color_accent)); + } + } + + /** + * sets the coloring of the given progress bar to color_accent. + * + * @param progressBar the progress bar to be colored + * @param color the color to be used + */ + public static void colorHorizontalProgressBar(ProgressBar progressBar, @ColorInt int color) { + if (progressBar != null) { progressBar.getIndeterminateDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN); progressBar.getProgressDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN); } @@ -301,9 +327,9 @@ public static void colorPreLollipopHorizontalSeekBar(SeekBar seekBar) { } /** - * set the owncloud standard colors for the snackbar. + * set the Nextcloud standard colors for the snackbar. * - * @param context the context relevant for setting the color according to the context's theme + * @param context the context relevant for setting the color according to the context's theme * @param snackbar the snackbar to be colored */ public static void colorSnackbar(Context context, Snackbar snackbar) { @@ -315,7 +341,7 @@ public static void colorSnackbar(Context context, Snackbar snackbar) { * Sets the color of the status bar to {@code color} on devices with OS version lollipop or higher. * * @param fragmentActivity fragment activity - * @param color the color + * @param color the color */ public static void colorStatusBar(FragmentActivity fragmentActivity, @ColorInt int color) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { @@ -326,11 +352,11 @@ public static void colorStatusBar(FragmentActivity fragmentActivity, @ColorInt i /** * Sets the color of the progressbar to {@code color} within the given toolbar. * - * @param activity the toolbar activity instance + * @param activity the toolbar activity instance * @param progressBarColor the color to be used for the toolbar's progress bar */ public static void colorToolbarProgressBar(FragmentActivity activity, int progressBarColor) { - if(activity instanceof ToolbarActivity) { + if (activity instanceof ToolbarActivity) { ((ToolbarActivity) activity).setProgressBarBackgroundColor(progressBarColor); } }