diff --git a/core/css/icons.scss b/core/css/icons.scss index 7c47a3e12c875..6d855381fae99 100644 --- a/core/css/icons.scss +++ b/core/css/icons.scss @@ -493,16 +493,23 @@ img, object, video, button, textarea, input, select { .icon-category-installed { background-image: url('../img/actions/user.svg?v=1'); } + .icon-category-enabled { background-image: url('../img/actions/checkmark.svg?v=1'); } + .icon-category-disabled { background-image: url('../img/actions/close.svg?v=1'); } + .icon-category-app-bundles { background-image: url('../img/categories/bundles.svg?v=1'); } +.icon-category-updates { + background-image: url('../img/actions/download.svg?v=1'); +} + .icon-category-files { background-image: url('../img/categories/files.svg?v=1'); } diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php index a33c9be20fc58..627107a63a84f 100644 --- a/lib/private/legacy/app.php +++ b/lib/private/legacy/app.php @@ -768,6 +768,8 @@ public static function getAllApps() { } } + $apps = array_unique($apps); + return $apps; } diff --git a/settings/Controller/AppSettingsController.php b/settings/Controller/AppSettingsController.php index ac77b2e7dd6ef..da59461e5fa79 100644 --- a/settings/Controller/AppSettingsController.php +++ b/settings/Controller/AppSettingsController.php @@ -52,6 +52,7 @@ class AppSettingsController extends Controller { const CAT_DISABLED = 1; const CAT_ALL_INSTALLED = 2; const CAT_APP_BUNDLES = 3; + const CAT_UPDATES = 4; /** @var \OCP\IL10N */ private $l10n; @@ -130,8 +131,10 @@ public function viewApps($category = '') { private function getAllCategories() { $currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2); + $updateCount = count($this->getAppsWithUpdates()); $formattedCategories = [ ['id' => self::CAT_ALL_INSTALLED, 'ident' => 'installed', 'displayName' => (string)$this->l10n->t('Your apps')], + ['id' => self::CAT_UPDATES, 'ident' => 'updates', 'displayName' => (string)$this->l10n->t('Updates'), 'counter' => $updateCount], ['id' => self::CAT_ENABLED, 'ident' => 'enabled', 'displayName' => (string)$this->l10n->t('Enabled apps')], ['id' => self::CAT_DISABLED, 'ident' => 'disabled', 'displayName' => (string)$this->l10n->t('Disabled apps')], ['id' => self::CAT_APP_BUNDLES, 'ident' => 'app-bundles', 'displayName' => (string)$this->l10n->t('App bundles')], @@ -273,6 +276,28 @@ private function getAppsForCategory($requestedCategory) { return $formattedApps; } + private function getAppsWithUpdates() { + $appClass = new \OC_App(); + $apps = $appClass->listAllApps(); + foreach($apps as $key => $app) { + $newVersion = \OC\Installer::isUpdateAvailable($app['id'], $this->appFetcher); + if($newVersion !== false) { + $apps[$key]['update'] = $newVersion; + } else { + unset($apps[$key]); + } + } + usort($apps, function ($a, $b) { + $a = (string)$a['name']; + $b = (string)$b['name']; + if ($a === $b) { + return 0; + } + return ($a < $b) ? -1 : 1; + }); + return $apps; + } + /** * Get all available apps in a category * @@ -301,6 +326,10 @@ public function listApps($category = '') { return ($a < $b) ? -1 : 1; }); break; + // updates + case 'updates': + $apps = $this->getAppsWithUpdates(); + break; // enabled apps case 'enabled': $apps = $appClass->listAllApps(); diff --git a/settings/js/apps.js b/settings/js/apps.js index 39e800636cb04..6406e37cbcbcd 100644 --- a/settings/js/apps.js +++ b/settings/js/apps.js @@ -58,8 +58,15 @@ OC.Settings.Apps = OC.Settings.Apps || { type:'GET', success:function (jsondata) { var html = template(jsondata); + var updateCategory = $.grep(jsondata, function(element, index) { + return element.ident === 'updates' + }); $('#apps-categories').html(html); $('#app-category-' + OC.Settings.Apps.State.currentCategory).addClass('active'); + if (updateCategory.length === 1) { + OC.Settings.Apps.State.availableUpdates = updateCategory[0].counter; + OC.Settings.Apps.refreshUpdateCounter(); + } }, complete: function() { $('#app-navigation').removeClass('icon-loading'); @@ -84,7 +91,6 @@ OC.Settings.Apps = OC.Settings.Apps || { $('#app-category-' + OC.Settings.Apps.State.currentCategory).removeClass('active'); $('#app-category-' + categoryId).addClass('active'); OC.Settings.Apps.State.currentCategory = categoryId; - OC.Settings.Apps.State.availableUpdates = 0; this._loadCategoryCall = $.ajax(OC.generateUrl('settings/apps/list?category={categoryId}', { categoryId: categoryId @@ -99,7 +105,7 @@ OC.Settings.Apps = OC.Settings.Apps || { return _.extend({level: 0}, app); }); var source; - if (categoryId === 'enabled' || categoryId === 'disabled' || categoryId === 'installed' || categoryId === 'app-bundles') { + if (categoryId === 'enabled' || categoryId === 'updates' || categoryId === 'disabled' || categoryId === 'installed' || categoryId === 'app-bundles') { source = $("#app-template-installed").html(); $('#apps-list').addClass('installed'); } else { @@ -122,6 +128,7 @@ OC.Settings.Apps = OC.Settings.Apps || { } var firstExperimental = false; + var hasNewUpdates = false; _.each(appList, function(app) { if(app.level === 0 && firstExperimental === false) { firstExperimental = true; @@ -131,19 +138,28 @@ OC.Settings.Apps = OC.Settings.Apps || { } if (app.update) { + hasNewUpdates = true; var $update = $('#app-' + app.id + ' .update'); $update.removeClass('hidden'); $update.val(t('settings', 'Update to %s').replace(/%s/g, app.update)); - OC.Settings.Apps.State.availableUpdates++; } }); - - if (OC.Settings.Apps.State.availableUpdates > 0) { - OC.Settings.Apps.State.$updateNotification = OC.Notification.show(n('settings', 'You have %n app update pending', 'You have %n app updates pending', OC.Settings.Apps.State.availableUpdates)); + // reload updates if a list with new updates is loaded + if (hasNewUpdates) { + OC.Settings.Apps.reloadUpdates(); + } else { + // hide update category after all updates are installed + // and the user is switching away from the empty updates view + OC.Settings.Apps.refreshUpdateCounter(); } } else { - $('#apps-list').addClass('hidden'); - $('#apps-list-empty').removeClass('hidden').find('h2').text(t('settings', 'No apps found for your version')); + if (categoryId === 'updates') { + OC.Settings.Apps.showEmptyUpdates(); + } else { + $('#apps-list').addClass('hidden'); + $('#apps-list-empty').removeClass('hidden').find('h2').text(t('settings', 'No apps found for your version')); + $('#app-list-empty-icon').addClass('icon-search').removeClass('icon-download'); + } } $('.enable.needs-download').tooltip({ @@ -517,6 +533,12 @@ OC.Settings.Apps = OC.Settings.Apps || { } }, + showEmptyUpdates: function() { + $('#apps-list').addClass('hidden'); + $('#apps-list-empty').removeClass('hidden').find('h2').text(t('settings', 'No app updates available')); + $('#app-list-empty-icon').removeClass('icon-search').addClass('icon-download'); + }, + updateApp:function(appId, element) { var oldButtonText = element.val(); element.val(t('settings','Updating....')); @@ -539,13 +561,14 @@ OC.Settings.Apps = OC.Settings.Apps || { var $version = $('#app-' + appId + ' .app-version'); $version.text(OC.Settings.Apps.State.apps[appId]['update']); - if (OC.Settings.Apps.State.$updateNotification) { - OC.Notification.hide(OC.Settings.Apps.State.$updateNotification); - } - OC.Settings.Apps.State.availableUpdates--; - if (OC.Settings.Apps.State.availableUpdates > 0) { - OC.Settings.Apps.State.$updateNotification = OC.Notification.show(n('settings', 'You have %n app update pending', 'You have %n app updates pending', OC.Settings.Apps.State.availableUpdates)); + OC.Settings.Apps.refreshUpdateCounter(); + + if (OC.Settings.Apps.State.currentCategory === 'updates') { + $('#app-' + appId).remove(); + if (OC.Settings.Apps.State.availableUpdates === 0) { + OC.Settings.Apps.showEmptyUpdates(); + } } } },'json'); @@ -656,6 +679,33 @@ OC.Settings.Apps = OC.Settings.Apps || { }); }, + reloadUpdates: function() { + if (this._loadUpdatesCall) { + this._loadUpdatesCall.abort(); + } + this._loadUpdatesCall = $.ajax(OC.generateUrl('settings/apps/list?category=updates'), { + type:'GET', + success: function (apps) { + OC.Settings.Apps.State.availableUpdates = apps.apps.length; + OC.Settings.Apps.refreshUpdateCounter(); + } + }); + }, + + refreshUpdateCounter: function() { + var $appCategoryUpdates = $('#app-category-updates'); + var $updateCount = $appCategoryUpdates.find('.app-navigation-entry-utils-counter'); + if (OC.Settings.Apps.State.availableUpdates > 0) { + $updateCount.html(OC.Settings.Apps.State.availableUpdates); + $appCategoryUpdates.show(); + } else { + $updateCount.empty(); + if (OC.Settings.Apps.State.currentCategory !== 'updates') { + $appCategoryUpdates.hide(); + } + } + }, + showErrorMessage: function(appId, message) { $('div#app-'+appId+' .warning') .show() @@ -703,6 +753,7 @@ OC.Settings.Apps = OC.Settings.Apps || { filter: function(query) { var $appList = $('#apps-list'), $emptyList = $('#apps-list-empty'); + $('#app-list-empty-icon').addClass('icon-search').removeClass('icon-download'); $appList.removeClass('hidden'); $appList.find('.section').removeClass('hidden'); $emptyList.addClass('hidden'); diff --git a/settings/templates/apps.php b/settings/templates/apps.php index 91a73fcbe5685..5d5194965985b 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -19,6 +19,11 @@ {{#each this}}
  • {{displayName}} +
    + +
  • {{/each}} @@ -65,9 +70,6 @@
    - {{#if canUnInstall}} @@ -206,7 +208,7 @@
    diff --git a/tests/Settings/Controller/AppSettingsControllerTest.php b/tests/Settings/Controller/AppSettingsControllerTest.php index 9633c77159698..e264d0dfbfe06 100644 --- a/tests/Settings/Controller/AppSettingsControllerTest.php +++ b/tests/Settings/Controller/AppSettingsControllerTest.php @@ -101,6 +101,12 @@ public function testListCategories() { 'ident' => 'installed', 'displayName' => 'Your apps', ], + [ + 'id' => 4, + 'ident' => 'updates', + 'displayName' => 'Updates', + 'counter' => 0, + ], [ 'id' => 0, 'ident' => 'enabled',