Support for android 8+, selectable download location, permissions check, recursive delete

This commit is contained in:
Mark Cooper
2021-09-19 18:44:10 -05:00
parent b7b6ef57ce
commit 094da4be4f
23 changed files with 798 additions and 387 deletions
+14 -7
View File
@@ -3,12 +3,7 @@
<template v-for="(shelf, index) in groupedBooks">
<div :key="index" class="border-b border-opacity-10 w-full bookshelfRow py-4 flex justify-around relative">
<template v-for="audiobook in shelf">
<!-- <div :key="audiobook.id" class="relative px-4"> -->
<cards-book-card :key="audiobook.id" :audiobook="audiobook" :width="cardWidth" :user-progress="userAudiobooks[audiobook.id]" :local-user-progress="localUserAudiobooks[audiobook.id]" />
<!-- <nuxt-link :to="`/audiobook/${audiobook.id}`">
<cards-book-cover :audiobook="audiobook" :width="cardWidth" class="mx-auto -mb-px" style="box-shadow: 4px 1px 8px #11111166, -4px 1px 8px #11111166, 1px -4px 8px #11111166" />
</nuxt-link> -->
<!-- </div> -->
</template>
<div class="bookshelfDivider h-4 w-full absolute bottom-0 left-0 right-0 z-10" />
</div>
@@ -93,16 +88,22 @@ export default {
} else {
console.log('Disconnected - Reset to local storage')
this.$store.commit('audiobooks/reset')
this.$store.dispatch('downloads/loadFromStorage')
this.$store.dispatch('audiobooks/useDownloaded')
// this.calcShelves()
// this.$store.dispatch('downloads/loadFromStorage')
}
}
},
mounted() {
this.$store.commit('audiobooks/addListener', { id: 'bookshelf', meth: this.audiobooksUpdated })
this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated })
window.addEventListener('resize', this.resize)
if (!this.$server) {
console.error('Bookshelf mounted no server')
return
}
this.$server.on('connected', this.socketConnected)
if (this.$server.connected) {
this.$store.dispatch('audiobooks/load')
@@ -115,6 +116,12 @@ export default {
this.$store.commit('audiobooks/removeListener', 'bookshelf')
this.$store.commit('user/removeSettingsListener', 'bookshelf')
window.removeEventListener('resize', this.resize)
if (!this.$server) {
console.error('Bookshelf beforeDestroy no server')
return
}
this.$server.off('connected', this.socketConnected)
}
}
</script>
+15 -18
View File
@@ -128,6 +128,7 @@ export default {
if (this.$refs.audioPlayerMini) {
this.$refs.audioPlayerMini.terminateStream()
}
this.download = null
this.$store.commit('setPlayingDownload', null)
this.$localStore.setCurrent(null)
@@ -167,12 +168,6 @@ export default {
}
if (this.$server.connected) {
// var updateObj = {
// audiobookId: this.download.id,
// totalDuration: this.download.audiobook.duration,
// clientCurrentTime: currentTime,
// clientProgress: Number((currentTime / this.download.audiobook.duration).toFixed(3))
// }
this.$server.socket.emit('progress_update', progressUpdate)
}
this.$localStore.updateUserAudiobookProgress(progressUpdate).then(() => {
@@ -184,7 +179,6 @@ export default {
closeStream() {},
streamClosed(audiobookId) {
console.log('Stream Closed')
if (this.stream.audiobook.id === audiobookId || audiobookId === 'n/a') {
this.$store.commit('setStreamAudiobook', null)
}
@@ -305,23 +299,15 @@ export default {
if (this.playingDownload) {
console.log('[StreamContainer] Play download on audio mount')
if (!this.download) {
this.download = { ...this.playingDownload }
}
this.playDownload()
} else if (this.$server.stream) {
console.log('[StreamContainer] Open stream on audio mount')
this.streamOpen(this.$server.stream)
}
},
setListeners() {
if (!this.$server.socket) {
console.error('Invalid server socket not set')
return
}
this.$server.socket.on('stream_open', this.streamOpen)
this.$server.socket.on('stream_closed', this.streamClosed)
this.$server.socket.on('stream_progress', this.streamProgress)
this.$server.socket.on('stream_ready', this.streamReady)
this.$server.socket.on('stream_reset', this.streamReset)
},
changePlaybackSpeed(speed) {
this.$store.dispatch('user/updateUserSettings', { playbackRate: speed })
},
@@ -342,6 +328,17 @@ export default {
this.cancelStream()
}
}
},
setListeners() {
if (!this.$server.socket) {
console.error('Invalid server socket not set')
return
}
this.$server.socket.on('stream_open', this.streamOpen)
this.$server.socket.on('stream_closed', this.streamClosed)
this.$server.socket.on('stream_progress', this.streamProgress)
this.$server.socket.on('stream_ready', this.streamReady)
this.$server.socket.on('stream_reset', this.streamReset)
}
},
mounted() {
+1
View File
@@ -110,6 +110,7 @@ export default {
return this.download ? this.download.cover : null
},
download() {
return null
return this.$store.getters['downloads/getDownloadIfReady'](this.audiobookId)
},
errorText() {
+14 -4
View File
@@ -1,8 +1,16 @@
<template>
<div class="relative rounded-sm overflow-hidden" :style="{ height: width * 1.6 + 'px', width: width + 'px', maxWidth: width + 'px', minWidth: width + 'px' }">
<div class="w-full h-full relative">
<div v-if="showCoverBg" class="bg-primary absolute top-0 left-0 w-full h-full">
<div class="w-full h-full z-0" ref="coverBg" />
<div class="bg-primary absolute top-0 left-0 w-full h-full">
<!-- Blurred background for covers that dont fill -->
<div v-if="showCoverBg" class="w-full h-full z-0" ref="coverBg" />
<!-- Image Loading indicator -->
<div v-if="!isImageLoaded" class="w-full h-full flex items-center justify-center text-white">
<svg class="animate-spin w-12 h-12" viewBox="0 0 24 24">
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
</svg>
</div>
</div>
<img ref="cover" :src="fullCoverUrl" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-cover'" />
</div>
@@ -42,7 +50,8 @@ export default {
data() {
return {
imageFailed: false,
showCoverBg: false
showCoverBg: false,
isImageLoaded: false
}
},
watch: {
@@ -142,10 +151,11 @@ export default {
this.showCoverBg = false
}
}
this.isImageLoaded = true
},
imageError(err) {
console.error('ImgError', err)
this.imageFailed = true
console.error('ImgError', err, `SET IMAGE FAILED ${this.imageFailed}`)
}
}
}
+50 -3
View File
@@ -3,7 +3,17 @@
<div class="w-full h-full overflow-hidden absolute top-0 left-0 flex items-center justify-center" @click="show = false">
<p class="absolute top-6 left-2 text-2xl">Downloads</p>
<div class="w-full overflow-x-hidden overflow-y-auto bg-primary rounded-lg border border-white border-opacity-20" style="max-height: 75%" @click.stop>
<div class="absolute top-16 left-0 right-0 w-full flex items-center px-2 py-1" :class="hasStoragePermission ? '' : 'text-error'">
<span class="material-icons" @click="changeDownloadFolderClick">{{ hasStoragePermission ? 'folder' : 'error' }}</span>
<p v-if="hasStoragePermission" class="text-sm px-4" @click="changeDownloadFolderClick">{{ downloadFolderSimplePath || 'No Download Folder Selected' }}</p>
<p v-else class="text-sm px-4" @click="changeDownloadFolderClick">No Storage Permissions. Click here</p>
</div>
<div v-if="totalSize" class="absolute bottom-0 left-0 right-0 w-full py-3 text-center">
<p class="text-sm text-center text-gray-300">Total: {{ $bytesPretty(totalSize) }}</p>
</div>
<div class="w-full overflow-x-hidden overflow-y-auto bg-primary rounded-lg border border-white border-opacity-20 mt-10" style="max-height: 75%" @click.stop>
<div v-if="!totalDownloads" class="flex items-center justify-center h-40">
<p>No Downloads</p>
</div>
@@ -28,9 +38,10 @@
<div class="flex items-center justify-center">
<img v-if="download.cover" :src="download.cover" class="w-10 h-16 object-contain" />
<img v-else src="/book_placeholder.jpg" class="w-10 h-16 object-contain" />
<div class="pl-2 w-1/2">
<div class="pl-2 w-2/3">
<p class="font-normal truncate text-sm">{{ download.audiobook.book.title }}</p>
<p class="font-normal truncate text-xs text-gray-400">{{ download.audiobook.book.author }}</p>
<p class="font-normal truncate text-xs text-gray-400">{{ $bytesPretty(download.size) }}</p>
</div>
<div class="flex-grow" />
<div v-if="download.isIncomplete" class="shadow-sm text-warning flex items-center justify-center rounded-full mr-4">
@@ -71,11 +82,22 @@
<script>
import { Dialog } from '@capacitor/dialog'
import AudioDownloader from '@/plugins/audio-downloader'
export default {
data() {
return {
downloadingProgress: {}
downloadFolder: null,
downloadingProgress: {},
totalSize: 0
}
},
watch: {
async show(newValue) {
if (newValue) {
this.downloadFolder = await this.$localStore.getDownloadFolder()
this.setTotalSize()
}
}
},
computed: {
@@ -87,6 +109,12 @@ export default {
this.$store.commit('downloads/setShowModal', val)
}
},
hasStoragePermission() {
return this.$store.state.hasStoragePermission
},
downloadFolderSimplePath() {
return this.downloadFolder ? this.downloadFolder.simplePath : null
},
totalDownloads() {
return this.downloadsReady.length + this.orphanDownloads.length + this.downloadsDownloading.length
},
@@ -132,6 +160,25 @@ export default {
}
},
methods: {
setTotalSize() {
var totalSize = 0
this.downloadsReady.forEach((dl) => {
totalSize += dl.size && !isNaN(dl.size) ? Number(dl.size) : 0
})
this.totalSize = totalSize
},
async changeDownloadFolderClick() {
if (!this.hasStoragePermission) {
console.log('Requesting Storage Permission')
AudioDownloader.requestStoragePermission()
} else {
var folderObj = await AudioDownloader.selectFolder()
if (folderObj.error) {
return this.$toast.error(`Error: ${folderObj.error || 'Unknown Error'}`)
}
await this.$localStore.setDownloadFolder(folderObj)
}
},
updateDownloadProgress({ audiobookId, progress }) {
this.$set(this.downloadingProgress, audiobookId, progress)
},
+4
View File
@@ -43,6 +43,10 @@ export default {
text: 'Added At',
value: 'addedAt'
},
{
text: 'Volume #',
value: 'book.volumeNumber'
},
{
text: 'Duration',
value: 'duration'