diff --git a/apps/settings/css/settings.scss b/apps/settings/css/settings.scss
index 7b90a261c9077adedbf6a2d6d86f8081aea5da73..dc9f7b5741604463383e3c4fac843af1a36944ae 100644
--- a/apps/settings/css/settings.scss
+++ b/apps/settings/css/settings.scss
@@ -663,8 +663,6 @@ span.version {
 }
 
 .app-level {
-	margin-top: 8px;
-
 	span {
 		color: var(--color-text-maxcontrast);
 		background-color: transparent;
diff --git a/apps/settings/js/vue-0.js b/apps/settings/js/vue-0.js
index 4e5d6cda8e46c2e39e2de6d8925924b77583fae6..499281f8b3bef0b45d50fc81426030bfa5b257ce 100644
Binary files a/apps/settings/js/vue-0.js and b/apps/settings/js/vue-0.js differ
diff --git a/apps/settings/js/vue-0.js.map b/apps/settings/js/vue-0.js.map
index 642324ff6af4f315310907013b67a4094eb54aca..887fcc153d97c16616d15bee8b2503627c3604d3 100644
Binary files a/apps/settings/js/vue-0.js.map and b/apps/settings/js/vue-0.js.map differ
diff --git a/apps/settings/js/vue-5.js b/apps/settings/js/vue-5.js
index bd529a1dab503b1548cbf868fc8e5ecd1493bf26..d575663433859cc60fa6643be0f3c20edad18df0 100644
Binary files a/apps/settings/js/vue-5.js and b/apps/settings/js/vue-5.js differ
diff --git a/apps/settings/js/vue-5.js.map b/apps/settings/js/vue-5.js.map
index 7887add594ffa94c88b93785093f7471bd3f905f..5a0c11b7c700ecd3e082a92956e9a229f59962bf 100644
Binary files a/apps/settings/js/vue-5.js.map and b/apps/settings/js/vue-5.js.map differ
diff --git a/apps/settings/js/vue-6.js b/apps/settings/js/vue-6.js
index caffe98d91f79400974a19148ebb7f4c85b096d4..6fe5a2dae81ce297bc7c16cb7d52fb5b8e979089 100644
Binary files a/apps/settings/js/vue-6.js and b/apps/settings/js/vue-6.js differ
diff --git a/apps/settings/js/vue-6.js.map b/apps/settings/js/vue-6.js.map
index a23ad4f22780ffbae6a786ec4d8efb4b52b20732..5fc6c45729727473233a7357a49cd4ba39d5585a 100644
Binary files a/apps/settings/js/vue-6.js.map and b/apps/settings/js/vue-6.js.map differ
diff --git a/apps/settings/js/vue-7.js b/apps/settings/js/vue-7.js
index c918e823b17f73abeda7611c7667333aed273cba..2bb630b6652aa5406d73836065f38db9f18d1575 100644
Binary files a/apps/settings/js/vue-7.js and b/apps/settings/js/vue-7.js differ
diff --git a/apps/settings/js/vue-7.js.map b/apps/settings/js/vue-7.js.map
index 4be042dc8ef26c6f58aa3f2c88360c4b6eb62f27..21ce72178afacefe23507df619542cd2330ed921 100644
Binary files a/apps/settings/js/vue-7.js.map and b/apps/settings/js/vue-7.js.map differ
diff --git a/apps/settings/js/vue-8.js b/apps/settings/js/vue-8.js
index 7c2e4c9b1e84922a268953a75f8ac22aad9772fd..22bf3803fcf9e3522576266f41200ead96ef9b66 100644
Binary files a/apps/settings/js/vue-8.js and b/apps/settings/js/vue-8.js differ
diff --git a/apps/settings/js/vue-8.js.map b/apps/settings/js/vue-8.js.map
index 8ab4dcc14c7ebfc9e40c4585844c583afd8b2cd1..08f21d56c180537b5b19cdec687a317a13df9179 100644
Binary files a/apps/settings/js/vue-8.js.map and b/apps/settings/js/vue-8.js.map differ
diff --git a/apps/settings/js/vue-settings-apps-users-management.js b/apps/settings/js/vue-settings-apps-users-management.js
index 54f76e26a40ec8638ed5a5167dec07a75d57e11f..56ac2fe80d3bb3975174f43e28f6b388f188ca6d 100644
Binary files a/apps/settings/js/vue-settings-apps-users-management.js and b/apps/settings/js/vue-settings-apps-users-management.js differ
diff --git a/apps/settings/js/vue-settings-apps-users-management.js.map b/apps/settings/js/vue-settings-apps-users-management.js.map
index ebed48babe12c0b762d59cc37233a8d998f806fa..fe0b4cddda07dc99ee148ef72f97cacc8e704c07 100644
Binary files a/apps/settings/js/vue-settings-apps-users-management.js.map and b/apps/settings/js/vue-settings-apps-users-management.js.map differ
diff --git a/apps/settings/src/components/AppDetails.vue b/apps/settings/src/components/AppDetails.vue
index 2c99e9c0c928074c8b91ef9c8a72dfd3b277da0d..55519bf9f8054df9fced182b1b632a2130567b6e 100644
--- a/apps/settings/src/components/AppDetails.vue
+++ b/apps/settings/src/components/AppDetails.vue
@@ -21,116 +21,74 @@
   -->
 
 <template>
-	<div id="app-details-view" style="padding: 20px;">
-		<h2>
-			<div v-if="!app.preview" class="icon-settings-dark" />
-			<svg v-if="app.previewAsIcon && app.preview"
-				width="32"
-				height="32"
-				viewBox="0 0 32 32">
-				<defs><filter :id="filterId"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" /></filter></defs>
-				<image x="0"
-					y="0"
-					width="32"
-					height="32"
-					preserveAspectRatio="xMinYMin meet"
-					:filter="filterUrl"
-					:xlink:href="app.preview"
-					class="app-icon" />
-			</svg>
-			{{ app.name }}
-		</h2>
-		<img v-if="screenshotLoaded" :src="app.screenshot" width="100%">
-		<div v-if="app.level === 300 || app.level === 200 || hasRating" class="app-level">
-			<span v-if="app.level === 300"
-				v-tooltip.auto="t('settings', 'This app is supported via your current Nextcloud subscription.')"
-				class="supported icon-checkmark-color">
-				{{ t('settings', 'Supported') }}</span>
-			<span v-if="app.level === 200"
-				v-tooltip.auto="t('settings', 'Featured apps are developed by and within the community. They offer central functionality and are ready for production use.')"
-				class="official icon-checkmark">
-				{{ t('settings', 'Featured') }}</span>
-			<AppScore v-if="hasRating" :score="app.appstoreData.ratingOverall" />
-		</div>
-
-		<div v-if="author" class="app-author">
-			{{ t('settings', 'by') }}
-			<span v-for="(a, index) in author" :key="index">
-				<a v-if="a['@attributes'] && a['@attributes']['homepage']" :href="a['@attributes']['homepage']">{{ a['@value'] }}</a><span v-else-if="a['@value']">{{ a['@value'] }}</span><span v-else>{{ a }}</span><span v-if="index+1 < author.length">, </span>
-			</span>
-		</div>
-		<div v-if="licence" class="app-licence">
-			{{ licence }}
-		</div>
-		<div class="actions">
-			<div class="actions-buttons">
+	<div class="app-details">
+		<div class="app-details__actions">
+			<div v-if="app.active && canLimitToGroups(app)" class="app-details__actions-groups">
+				<input :id="prefix('groups_enable', app.id)"
+					v-model="groupCheckedAppsData"
+					type="checkbox"
+					:value="app.id"
+					class="groups-enable__checkbox checkbox"
+					@change="setGroupLimit">
+				<label :for="prefix('groups_enable', app.id)">{{ t('settings', 'Limit to groups') }}</label>
+				<input type="hidden"
+					class="group_select"
+					:title="t('settings', 'All')"
+					value="">
+				<Multiselect v-if="isLimitedToGroups(app)"
+					:options="groups"
+					:value="appGroups"
+					:options-limit="5"
+					:placeholder="t('settings', 'Limit app usage to groups')"
+					label="name"
+					track-by="id"
+					class="multiselect-vue"
+					:multiple="true"
+					:close-on-select="false"
+					:tag-width="60"
+					@select="addGroupLimitation"
+					@remove="removeGroupLimitation"
+					@search-change="asyncFindGroup">
+					<span slot="noResult">{{ t('settings', 'No results') }}</span>
+				</Multiselect>
+			</div>
+			<div class="app-details__actions-manage">
 				<input v-if="app.update"
 					class="update primary"
 					type="button"
-					:value="t('settings', 'Update to {version}', {version: app.update})"
-					:disabled="installing || loading(app.id)"
+					:value="t('settings', 'Update to {version}', { version: app.update })"
+					:disabled="installing || isLoading"
 					@click="update(app.id)">
 				<input v-if="app.canUnInstall"
 					class="uninstall"
 					type="button"
 					:value="t('settings', 'Remove')"
-					:disabled="installing || loading(app.id)"
+					:disabled="installing || isLoading"
 					@click="remove(app.id)">
 				<input v-if="app.active"
 					class="enable"
 					type="button"
 					:value="t('settings','Disable')"
-					:disabled="installing || loading(app.id)"
+					:disabled="installing || isLoading"
 					@click="disable(app.id)">
 				<input v-if="!app.active && (app.canInstall || app.isCompatible)"
 					v-tooltip.auto="enableButtonTooltip"
 					class="enable primary"
 					type="button"
 					:value="enableButtonText"
-					:disabled="!app.canInstall || installing || loading(app.id)"
+					:disabled="!app.canInstall || installing || isLoading"
 					@click="enable(app.id)">
-				<input v-else-if="!app.active"
+				<input v-else-if="!app.active && !app.canInstall"
 					v-tooltip.auto="forceEnableButtonTooltip"
 					class="enable force"
 					type="button"
 					:value="forceEnableButtonText"
-					:disabled="installing || loading(app.id)"
+					:disabled="installing || isLoading"
 					@click="forceEnable(app.id)">
 			</div>
-			<div class="app-groups">
-				<div v-if="app.active && canLimitToGroups(app)" class="groups-enable">
-					<input :id="prefix('groups_enable', app.id)"
-						v-model="groupCheckedAppsData"
-						type="checkbox"
-						:value="app.id"
-						class="groups-enable__checkbox checkbox"
-						@change="setGroupLimit">
-					<label :for="prefix('groups_enable', app.id)">{{ t('settings', 'Limit to groups') }}</label>
-					<input type="hidden"
-						class="group_select"
-						:title="t('settings', 'All')"
-						value="">
-					<Multiselect v-if="isLimitedToGroups(app)"
-						:options="groups"
-						:value="appGroups"
-						:options-limit="5"
-						:placeholder="t('settings', 'Limit app usage to groups')"
-						label="name"
-						track-by="id"
-						class="multiselect-vue"
-						:multiple="true"
-						:close-on-select="false"
-						:tag-width="60"
-						@select="addGroupLimitation"
-						@remove="removeGroupLimitation"
-						@search-change="asyncFindGroup">
-						<span slot="noResult">{{ t('settings', 'No results') }}</span>
-					</Multiselect>
-				</div>
-			</div>
 		</div>
 
-		<ul class="app-dependencies">
+		<ul class="app-details__dependencies">
 			<li v-if="app.missingMinOwnCloudVersion">
 				{{ t('settings', 'This app has no minimum Nextcloud version assigned. This will be an error in the future.') }}
 			</li>
@@ -147,7 +105,7 @@
 			</li>
 		</ul>
 
-		<p class="documentation">
+		<p class="app-details__documentation">
 			<a v-if="!app.internal"
 				class="appslink"
 				:href="appstoreUrl"
@@ -182,7 +140,7 @@
 				rel="noreferrer noopener">{{ t('settings', 'Developer documentation') }} ↗</a>
 		</p>
 
-		<div class="app-description" v-html="renderMarkdown" />
+		<div class="app-details__description" v-html="renderMarkdown" />
 	</div>
 </template>
 
@@ -191,25 +149,30 @@ import { Multiselect } from '@nextcloud/vue'
 import marked from 'marked'
 import dompurify from 'dompurify'
 
-import AppScore from './AppList/AppScore'
-import AppManagement from './AppManagement'
+import AppManagement from '../mixins/AppManagement'
 import PrefixMixin from './PrefixMixin'
-import SvgFilterMixin from './SvgFilterMixin'
 
 export default {
 	name: 'AppDetails',
+
 	components: {
 		Multiselect,
-		AppScore,
 	},
-	mixins: [AppManagement, PrefixMixin, SvgFilterMixin],
-	props: ['category', 'app'],
+	mixins: [AppManagement, PrefixMixin],
+
+	props: {
+		app: {
+			type: Object,
+			required: true,
+		},
+	},
+
 	data() {
 		return {
 			groupCheckedAppsData: false,
-			screenshotLoaded: false,
 		}
 	},
+
 	computed: {
 		appstoreUrl() {
 			return `https://apps.nextcloud.com/apps/${this.app.id}`
@@ -220,9 +183,6 @@ export default {
 			}
 			return null
 		},
-		hasRating() {
-			return this.app.appstoreData && this.app.appstoreData.ratingNumOverall > 5
-		},
 		author() {
 			if (typeof this.app.author === 'string') {
 				return [
@@ -309,27 +269,49 @@ export default {
 		if (this.app.groups.length > 0) {
 			this.groupCheckedAppsData = true
 		}
-		if (this.app.screenshot) {
-			const image = new Image()
-			image.onload = (e) => {
-				this.screenshotLoaded = true
-			}
-			image.src = this.app.screenshot
-		}
 	},
 }
 </script>
 
-<style scoped>
-	.force {
-		background: var(--color-main-background);
-		border-color: var(--color-error);
-		color: var(--color-error);
+<style scoped lang="scss">
+.app-details {
+	padding: 20px;
+
+	&__actions {
+		// app management
+		&-manage {
+			// if too many, shrink them and ellipsis
+			display: flex;
+			input {
+				flex: 0 1 auto;
+				min-width: 0;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+				overflow: hidden;
+			}
+		}
+	}
+	&__dependencies {
+		opacity: .7;
 	}
-	.force:hover,
-	.force:active {
-		background: var(--color-error);
-		border-color: var(--color-error) !important;
-		color: var(--color-main-background);
+	&__documentation {
+		padding-top: 20px;
 	}
+	&__description {
+		padding-top: 20px;
+	}
+}
+
+.force {
+	color: var(--color-error);
+	border-color: var(--color-error);
+	background: var(--color-main-background);
+}
+.force:hover,
+.force:active {
+	color: var(--color-main-background);
+	border-color: var(--color-error) !important;
+	background: var(--color-error);
+}
+
 </style>
diff --git a/apps/settings/src/components/AppList/AppItem.vue b/apps/settings/src/components/AppList/AppItem.vue
index eeaa3ae38993dd903f923c747c984059b6cb3126..41ba0d9578ad200c9a4ec9f19e3669049a8e8ec7 100644
--- a/apps/settings/src/components/AppList/AppItem.vue
+++ b/apps/settings/src/components/AppList/AppItem.vue
@@ -69,38 +69,38 @@
 			<div v-if="app.error" class="warning">
 				{{ app.error }}
 			</div>
-			<div v-if="loading(app.id)" class="icon icon-loading-small" />
+			<div v-if="isLoading" class="icon icon-loading-small" />
 			<input v-if="app.update"
 				class="update primary"
 				type="button"
 				:value="t('settings', 'Update to {update}', {update:app.update})"
-				:disabled="installing || loading(app.id)"
+				:disabled="installing || isLoading"
 				@click.stop="update(app.id)">
 			<input v-if="app.canUnInstall"
 				class="uninstall"
 				type="button"
 				:value="t('settings', 'Remove')"
-				:disabled="installing || loading(app.id)"
+				:disabled="installing || isLoading"
 				@click.stop="remove(app.id)">
 			<input v-if="app.active"
 				class="enable"
 				type="button"
 				:value="t('settings','Disable')"
-				:disabled="installing || loading(app.id)"
+				:disabled="installing || isLoading"
 				@click.stop="disable(app.id)">
 			<input v-if="!app.active && (app.canInstall || app.isCompatible)"
 				v-tooltip.auto="enableButtonTooltip"
 				class="enable"
 				type="button"
 				:value="enableButtonText"
-				:disabled="!app.canInstall || installing || loading(app.id)"
+				:disabled="!app.canInstall || installing || isLoading"
 				@click.stop="enable(app.id)">
 			<input v-else-if="!app.active"
 				v-tooltip.auto="forceEnableButtonTooltip"
 				class="enable force"
 				type="button"
 				:value="forceEnableButtonText"
-				:disabled="installing || loading(app.id)"
+				:disabled="installing || isLoading"
 				@click.stop="forceEnable(app.id)">
 		</div>
 	</div>
@@ -108,7 +108,7 @@
 
 <script>
 import AppScore from './AppScore'
-import AppManagement from '../AppManagement'
+import AppManagement from '../../mixins/AppManagement'
 import SvgFilterMixin from '../SvgFilterMixin'
 
 export default {
diff --git a/apps/settings/src/components/AppManagement.vue b/apps/settings/src/mixins/AppManagement.js
similarity index 75%
rename from apps/settings/src/components/AppManagement.vue
rename to apps/settings/src/mixins/AppManagement.js
index 6bf1eee83cf47c4411f8fa985bf9b372fa8e5e5b..0bdb238601d13010af9f3f89fee221d15974f9c1 100644
--- a/apps/settings/src/components/AppManagement.vue
+++ b/apps/settings/src/mixins/AppManagement.js
@@ -1,40 +1,37 @@
-<!--
-  - @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
-  -
-  - @author Julius Härtl <jus@bitgrid.net>
-  -
-  - @license GNU AGPL version 3 or any later version
-  -
-  - This program is free software: you can redistribute it and/or modify
-  - it under the terms of the GNU Affero General Public License as
-  - published by the Free Software Foundation, either version 3 of the
-  - License, or (at your option) 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 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 <http://www.gnu.org/licenses/>.
-  -
-  -->
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) 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 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 <http://www.gnu.org/licenses/>.
+ *
+ */
 
-<script>
 export default {
 	computed: {
 		appGroups() {
 			return this.app.groups.map(group => { return { id: group, name: group } })
 		},
-		loading() {
-			const self = this
-			return function(id) {
-				return self.$store.getters.loading(id)
-			}
-		},
 		installing() {
 			return this.$store.getters.loading('install')
 		},
+		isLoading() {
+			return this.app && this.$store.getters.loading(this.app.id)
+		},
 		enableButtonText() {
 			if (this.app.needsDownload) {
 				return t('settings', 'Download and enable')
@@ -61,11 +58,19 @@ export default {
 			return base
 		},
 	},
+
+	data() {
+		return {
+			groupCheckedAppsData: false,
+		}
+	},
+
 	mounted() {
-		if (this.app.groups.length > 0) {
+		if (this.app && this.app.groups && this.app.groups.length > 0) {
 			this.groupCheckedAppsData = true
 		}
 	},
+
 	methods: {
 		asyncFindGroup(query) {
 			return this.$store.dispatch('getGroups', { search: query, limit: 5, offset: 0 })
@@ -135,4 +140,3 @@ export default {
 		},
 	},
 }
-</script>
diff --git a/apps/settings/src/views/Apps.vue b/apps/settings/src/views/Apps.vue
index 2c7dd9dae0efdb5f312acf3f3e283e0101b37a16..313c58afbd96077768d60a0d12de9614663219e9 100644
--- a/apps/settings/src/views/Apps.vue
+++ b/apps/settings/src/views/Apps.vue
@@ -22,9 +22,10 @@
 
 <template>
 	<Content app-name="settings"
-		:class="{ 'with-app-sidebar': currentApp}"
+		:class="{ 'with-app-sidebar': app}"
 		:content-class="{ 'icon-loading': loadingList }"
 		:navigation-class="{ 'icon-loading': loading }">
+		<!-- Categories & filters -->
 		<AppNavigation>
 			<template #list>
 				<AppNavigationItem
@@ -86,11 +87,39 @@
 					:title="t('settings', 'Developer documentation') + ' ↗'" />
 			</template>
 		</AppNavigation>
+
+		<!-- Apps list -->
 		<AppContent class="app-settings-content" :class="{ 'icon-loading': loadingList }">
-			<AppList :category="category" :app="currentApp" :search="searchQuery" />
+			<AppList :category="category" :app="app" :search="searchQuery" />
 		</AppContent>
-		<AppSidebar v-if="id && currentApp" @close="hideAppDetails">
-			<AppDetails :category="category" :app="currentApp" />
+
+		<!-- Selected app details -->
+		<AppSidebar
+			v-if="id && app"
+			v-bind="appSidebar"
+			:class="{'app-sidebar--without-background': !appSidebar.background}"
+			@close="hideAppDetails">
+			<template v-if="!appSidebar.background" #header>
+				<div class="app-sidebar-header__figure--default-app-icon icon-settings-dark" />
+			</template>
+
+			<template #primary-actions>
+				<!-- Featured/Supported badges -->
+				<div v-if="app.level === 300 || app.level === 200 || hasRating" class="app-level">
+					<span v-if="app.level === 300"
+						v-tooltip.auto="t('settings', 'This app is supported via your current Nextcloud subscription.')"
+						class="supported icon-checkmark-color">
+						{{ t('settings', 'Supported') }}</span>
+					<span v-if="app.level === 200"
+						v-tooltip.auto="t('settings', 'Featured apps are developed by and within the community. They offer central functionality and are ready for production use.')"
+						class="official icon-checkmark">
+						{{ t('settings', 'Featured') }}</span>
+					<AppScore v-if="hasRating" :score="app.appstoreData.ratingOverall" />
+				</div>
+			</template>
+
+			<!-- Tab content -->
+			<AppDetails :app="app" />
 		</AppSidebar>
 	</Content>
 </template>
@@ -108,11 +137,14 @@ import VueLocalStorage from 'vue-localstorage'
 
 import AppList from '../components/AppList'
 import AppDetails from '../components/AppDetails'
+import AppManagement from '../mixins/AppManagement'
+import AppScore from '../components/AppList/AppScore'
 
 Vue.use(VueLocalStorage)
 
 export default {
 	name: 'Apps',
+
 	components: {
 		AppContent,
 		AppDetails,
@@ -121,9 +153,13 @@ export default {
 		AppNavigationCounter,
 		AppNavigationItem,
 		AppNavigationSpacer,
+		AppScore,
 		AppSidebar,
 		Content,
 	},
+
+	mixins: [AppManagement],
+
 	props: {
 		category: {
 			type: String,
@@ -134,11 +170,14 @@ export default {
 			default: '',
 		},
 	},
+
 	data() {
 		return {
 			searchQuery: '',
+			screenshotLoaded: false,
 		}
 	},
+
 	computed: {
 		loading() {
 			return this.$store.getters.loading('categories')
@@ -146,7 +185,7 @@ export default {
 		loadingList() {
 			return this.$store.getters.loading('list')
 		},
-		currentApp() {
+		app() {
 			return this.apps.find(app => app.id === this.id)
 		},
 		categories() {
@@ -161,12 +200,53 @@ export default {
 		settings() {
 			return this.$store.getters.getServerData
 		},
+
+		hasRating() {
+			return this.app.appstoreData && this.app.appstoreData.ratingNumOverall > 5
+		},
+
+		// sidebar app binding
+		appSidebar() {
+			const author = Array.isArray(this.app.author)
+				? this.app.author[0]['@value']
+					? this.app.author.map(author => author['@value']).join(', ')
+					: this.app.author.join(', ')
+				: this.app.author['@value']
+					? this.app.author['@value']
+					: this.app.author
+			const license = t('settings', '{license}-licensed', { license: ('' + this.app.licence).toUpperCase() })
+
+			const subtitle = t('settings', 'by {author}\n{license}', { author, license })
+
+			return {
+				subtitle,
+				background: this.app.screenshot && this.screenshotLoaded
+					? this.app.screenshot
+					: this.app.preview,
+				compact: !(this.app.screenshot && this.screenshotLoaded),
+				title: this.app.name,
+
+			}
+		},
 	},
+
 	watch: {
 		category(val, old) {
 			this.setSearch('')
 		},
+
+		app() {
+			this.screenshotLoaded = false
+			if (this.app && this.app.screenshot) {
+				const image = new Image()
+				image.onload = (e) => {
+					this.screenshotLoaded = true
+				}
+				image.src = this.app.screenshot
+			}
+		},
 	},
+
 	beforeMount() {
 		this.$store.dispatch('getCategories')
 		this.$store.dispatch('getAllApps')
@@ -179,6 +259,7 @@ export default {
 		 */
 		this.appSearch = new OCA.Search(this.setSearch, this.resetSearch)
 	},
+
 	methods: {
 		setSearch(query) {
 			this.searchQuery = query
@@ -195,3 +276,54 @@ export default {
 	},
 }
 </script>
+
+<style lang="scss" scoped>
+.app-sidebar::v-deep {
+	&:not(.app-sidebar--without-background) {
+		// with full screenshot, let's fill the figure
+		:not(.app-sidebar-header--compact) .app-sidebar-header__figure {
+			background-size: cover;
+		}
+		// revert sidebar app icon so it is black
+		.app-sidebar-header--compact .app-sidebar-header__figure {
+			background-size: 32px;
+
+			filter: invert(1);
+		}
+	}
+
+	// default icon slot styling
+	&.app-sidebar--without-background {
+		.app-sidebar-header__figure {
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			&--default-app-icon {
+				width: 32px;
+				height: 32px;
+				background-size: 32px;
+			}
+		}
+	}
+
+	// TODO: migrate to components
+	.app-sidebar-header__desc {
+		// allow multi line subtitle for the license
+		.app-sidebar-header__subtitle {
+			overflow: visible !important;
+			height: auto;
+			white-space: normal !important;
+			line-height: 16px;
+		}
+	}
+
+	.app-sidebar-header__action {
+		// align with tab content
+		margin: 0 20px;
+		input {
+			margin: 3px;
+		}
+	}
+}
+
+</style>