Update:Android download to internal storage option #635

This commit is contained in:
advplyr 2023-06-04 14:59:55 -05:00
parent fbcb8620f9
commit 373221703d
12 changed files with 126 additions and 71 deletions

View file

@ -233,7 +233,7 @@ class AbsDownloader : Plugin() {
val fileSize = audioTrack?.metadata?.size ?: 0 val fileSize = audioTrack?.metadata?.size ?: 0
Log.d(tag, "Starting podcast episode download") Log.d(tag, "Starting podcast episode download")
val itemFolderPath = localFolder.absolutePath + "/" + podcastTitle val itemFolderPath = if (isInternal) "$tempFolderPath" else "${localFolder.absolutePath}/$podcastTitle"
val downloadItemId = "${libraryItem.id}-${episode?.id}" val downloadItemId = "${libraryItem.id}-${episode?.id}"
val downloadItem = DownloadItem(downloadItemId, libraryItem.id, episode?.id, libraryItem.userMediaProgress, DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, podcastTitle, podcastTitle, libraryItem.media, mutableListOf()) val downloadItem = DownloadItem(downloadItemId, libraryItem.id, episode?.id, libraryItem.userMediaProgress, DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, podcastTitle, podcastTitle, libraryItem.media, mutableListOf())

View file

@ -1,7 +1,6 @@
package com.audiobookshelf.app.plugins package com.audiobookshelf.app.plugins
import android.app.AlertDialog import android.app.AlertDialog
import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
@ -165,6 +164,13 @@ class AbsFileSystem : Plugin() {
call.resolve(jsobj) call.resolve(jsobj)
} }
@PluginMethod
fun getSDKVersion(call: PluginCall) {
val jsObject = JSObject()
jsObject.put("version", Build.VERSION.SDK_INT)
call.resolve(jsObject)
}
@PluginMethod @PluginMethod
fun scanFolder(call: PluginCall) { fun scanFolder(call: PluginCall) {
val folderId = call.data.getString("folderId", "").toString() val folderId = call.data.getString("folderId", "").toString()

View file

@ -1,8 +1,8 @@
<template> <template>
<modals-modal v-model="show" :width="300" height="100%"> <modals-modal v-model="show" :width="300" height="100%">
<template #outer> <template #outer>
<div class="absolute top-8 left-4 z-40" style="max-width: 80%"> <div class="absolute top-10 left-4 z-40" style="max-width: 80%">
<p class="text-white text-lg truncate">Select Local Folder</p> <p class="text-white text-lg truncate">Select Download Location</p>
</div> </div>
</template> </template>
@ -25,36 +25,48 @@
<script> <script>
export default { export default {
props: {
value: Boolean,
mediaType: String
},
data() { data() {
return { return {
localFolders: [] localFolders: []
} }
}, },
watch: { watch: {
value(newVal) { show(newVal) {
if (newVal) {
this.$nextTick(this.init) this.$nextTick(this.init)
} }
}
}, },
computed: { computed: {
show: { show: {
get() { get() {
return this.value return this.$store.state.globals.showSelectLocalFolderModal
}, },
set(val) { set(val) {
this.$emit('input', val) this.$store.commit('globals/setShowSelectLocalFolderModal', val)
} }
},
modalData() {
return this.$store.state.globals.localFolderSelectData || {}
},
callback() {
return this.modalData.callback
},
mediaType() {
return this.modalData.mediaType
} }
}, },
methods: { methods: {
clickedOption(folder) { clickedOption(folder) {
this.$emit('select', folder) this.show = false
if (!this.callback) {
console.error('Callback not set')
return
}
this.callback(folder)
}, },
async init() { async init() {
var localFolders = (await this.$db.getLocalFolders()) || [] const localFolders = (await this.$db.getLocalFolders()) || []
if (!localFolders.some((lf) => lf.id === `internal-${this.mediaType}`)) { if (!localFolders.some((lf) => lf.id === `internal-${this.mediaType}`)) {
localFolders.push({ localFolders.push({

View file

@ -186,28 +186,32 @@ export default {
} }
}, },
async download(selectedLocalFolder = null) { async download(selectedLocalFolder = null) {
var localFolder = selectedLocalFolder let localFolder = selectedLocalFolder
if (!localFolder) { if (!localFolder) {
var localFolders = (await this.$db.getLocalFolders()) || [] const localFolders = (await this.$db.getLocalFolders()) || []
console.log('Local folders loaded', localFolders.length) console.log('Local folders loaded', localFolders.length)
var foldersWithMediaType = localFolders.filter((lf) => { const foldersWithMediaType = localFolders.filter((lf) => {
console.log('Checking local folder', lf.mediaType) console.log('Checking local folder', lf.mediaType)
return lf.mediaType == this.mediaType return lf.mediaType == this.mediaType
}) })
console.log('Folders with media type', this.mediaType, foldersWithMediaType.length) console.log('Folders with media type', this.mediaType, foldersWithMediaType.length)
const internalStorageFolder = foldersWithMediaType.find((f) => f.id === `internal-${this.mediaType}`)
if (!foldersWithMediaType.length) { if (!foldersWithMediaType.length) {
// No local folders or no local folders with this media type localFolder = {
localFolder = await this.selectFolder() id: `internal-${this.mediaType}`,
} else if (foldersWithMediaType.length == 1) { name: 'Internal App Storage',
console.log('Only 1 local folder with this media type - auto select it') mediaType: this.mediaType
localFolder = foldersWithMediaType[0]
} else {
console.log('Multiple folders with media type')
// this.showSelectLocalFolder = true
return
} }
if (!localFolder) { } else if (foldersWithMediaType.length === 1 && internalStorageFolder) {
return this.$toast.error('Invalid download folder') localFolder = internalStorageFolder
} else {
this.$store.commit('globals/showSelectLocalFolderModal', {
mediaType: this.mediaType,
callback: (folder) => {
this.download(folder)
}
})
return
} }
} }

View file

@ -192,28 +192,32 @@ export default {
} }
}, },
async download(selectedLocalFolder = null) { async download(selectedLocalFolder = null) {
var localFolder = selectedLocalFolder let localFolder = selectedLocalFolder
if (!localFolder) { if (!localFolder) {
var localFolders = (await this.$db.getLocalFolders()) || [] const localFolders = (await this.$db.getLocalFolders()) || []
console.log('Local folders loaded', localFolders.length) console.log('Local folders loaded', localFolders.length)
var foldersWithMediaType = localFolders.filter((lf) => { const foldersWithMediaType = localFolders.filter((lf) => {
console.log('Checking local folder', lf.mediaType) console.log('Checking local folder', lf.mediaType)
return lf.mediaType == this.mediaType return lf.mediaType == this.mediaType
}) })
console.log('Folders with media type', this.mediaType, foldersWithMediaType.length) console.log('Folders with media type', this.mediaType, foldersWithMediaType.length)
const internalStorageFolder = foldersWithMediaType.find((f) => f.id === `internal-${this.mediaType}`)
if (!foldersWithMediaType.length) { if (!foldersWithMediaType.length) {
// No local folders or no local folders with this media type localFolder = {
localFolder = await this.selectFolder() id: `internal-${this.mediaType}`,
} else if (foldersWithMediaType.length == 1) { name: 'Internal App Storage',
console.log('Only 1 local folder with this media type - auto select it') mediaType: this.mediaType
localFolder = foldersWithMediaType[0]
} else {
console.log('Multiple folders with media type')
// this.showSelectLocalFolder = true
return
} }
if (!localFolder) { } else if (foldersWithMediaType.length === 1 && internalStorageFolder) {
return this.$toast.error('Invalid download folder') localFolder = internalStorageFolder
} else {
this.$store.commit('globals/showSelectLocalFolderModal', {
mediaType: this.mediaType,
callback: (folder) => {
this.download(folder)
}
})
return
} }
} }

View file

@ -11,6 +11,7 @@
<app-audio-player-container ref="streamContainer" /> <app-audio-player-container ref="streamContainer" />
<modals-libraries-modal /> <modals-libraries-modal />
<modals-playlists-add-create-modal /> <modals-playlists-add-create-modal />
<modals-select-local-folder-modal />
<app-side-drawer /> <app-side-drawer />
<readers-reader /> <readers-reader />
</div> </div>

View file

@ -346,19 +346,23 @@ export default {
return lf.mediaType == this.mediaType return lf.mediaType == this.mediaType
}) })
console.log('Folders with media type', this.mediaType, foldersWithMediaType.length) console.log('Folders with media type', this.mediaType, foldersWithMediaType.length)
const internalStorageFolder = foldersWithMediaType.find((f) => f.id === `internal-${this.mediaType}`)
if (!foldersWithMediaType.length) { if (!foldersWithMediaType.length) {
// No local folders or no local folders with this media type localFolder = {
localFolder = await this.selectFolder() id: `internal-${this.mediaType}`,
} else if (foldersWithMediaType.length == 1) { name: 'Internal App Storage',
console.log('Only 1 local folder with this media type - auto select it') mediaType: this.mediaType
localFolder = foldersWithMediaType[0]
} else {
console.log('Multiple folders with media type')
// this.showSelectLocalFolder = true
return
} }
if (!localFolder) { } else if (foldersWithMediaType.length === 1 && internalStorageFolder) {
return this.$toast.error('Invalid download folder') localFolder = internalStorageFolder
} else {
this.$store.commit('globals/showSelectLocalFolderModal', {
mediaType: this.mediaType,
callback: (folder) => {
this.download(folder)
}
})
return
} }
} }

View file

@ -622,38 +622,32 @@ export default {
}, },
async download(selectedLocalFolder = null) { async download(selectedLocalFolder = null) {
// Get the local folder to download to // Get the local folder to download to
var localFolder = selectedLocalFolder let localFolder = selectedLocalFolder
if (!localFolder) { if (!localFolder) {
var localFolders = (await this.$db.getLocalFolders()) || [] const localFolders = (await this.$db.getLocalFolders()) || []
console.log('Local folders loaded', localFolders.length) console.log('Local folders loaded', localFolders.length)
var foldersWithMediaType = localFolders.filter((lf) => { const foldersWithMediaType = localFolders.filter((lf) => {
console.log('Checking local folder', lf.mediaType) console.log('Checking local folder', lf.mediaType)
return lf.mediaType == this.mediaType return lf.mediaType == this.mediaType
}) })
console.log('Folders with media type', this.mediaType, foldersWithMediaType.length) console.log('Folders with media type', this.mediaType, foldersWithMediaType.length)
const internalStorageFolder = foldersWithMediaType.find((f) => f.id === `internal-${this.mediaType}`)
if (!foldersWithMediaType.length) { if (!foldersWithMediaType.length) {
localFolder = { localFolder = {
id: `internal-${this.mediaType}`, id: `internal-${this.mediaType}`,
name: 'App Storage', name: 'Internal App Storage',
mediaType: this.mediaType mediaType: this.mediaType
} }
} else if (foldersWithMediaType.length === 1 && internalStorageFolder) {
localFolder = internalStorageFolder
} else { } else {
this.showSelectLocalFolder = true this.$store.commit('globals/showSelectLocalFolderModal', {
return mediaType: this.mediaType,
callback: (folder) => {
this.download(folder)
} }
// if (!foldersWithMediaType.length) { })
// // No local folders or no local folders with this media type return
// localFolder = await this.selectFolder()
// } else if (foldersWithMediaType.length == 1) {
// console.log('Only 1 local folder with this media type - auto select it')
// localFolder = foldersWithMediaType[0]
// } else {
// console.log('Multiple folders with media type')
// this.showSelectLocalFolder = true
// return
// }
if (!localFolder) {
return this.$toast.error('Invalid download folder')
} }
} }

View file

@ -15,12 +15,18 @@
<div v-if="!localFolders.length" class="flex justify-center"> <div v-if="!localFolders.length" class="flex justify-center">
<p class="text-center">No Media Folders</p> <p class="text-center">No Media Folders</p>
</div> </div>
<div class="flex border-t border-white border-opacity-10 my-4 py-4"> <div v-if="!isAndroid10OrBelow || overrideFolderRestriction" class="flex border-t border-white border-opacity-10 my-4 py-4">
<div class="flex-grow pr-1"> <div class="flex-grow pr-1">
<ui-dropdown v-model="newFolderMediaType" placeholder="Select media type" :items="mediaTypeItems" /> <ui-dropdown v-model="newFolderMediaType" placeholder="Select media type" :items="mediaTypeItems" />
</div> </div>
<ui-btn small class="w-28" color="success" @click="selectFolder">New Folder</ui-btn> <ui-btn small class="w-28" color="success" @click="selectFolder">New Folder</ui-btn>
</div> </div>
<div v-else class="flex border-t border-white border-opacity-10 my-4 py-4">
<div class="flex-grow pr-1">
<p class="text-sm">Android 10 and below will use internal app storage for downloads.</p>
</div>
<ui-btn small class="w-28" color="primary" @click="overrideFolderRestriction = true">Override</ui-btn>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -44,7 +50,9 @@ export default {
text: 'Podcasts' text: 'Podcasts'
} }
], ],
syncing: false syncing: false,
isAndroid10OrBelow: false,
overrideFolderRestriction: false
} }
}, },
computed: { computed: {
@ -82,6 +90,10 @@ export default {
this.$router.push(`/localMedia/folders/${folderObj.id}?scan=1`) this.$router.push(`/localMedia/folders/${folderObj.id}?scan=1`)
}, },
async init() { async init() {
const androidSdkVersion = await this.$getAndroidSDKVersion()
this.isAndroid10OrBelow = !!androidSdkVersion && androidSdkVersion <= 29
console.log(`androidSdkVersion=${androidSdkVersion}, isAndroid10OrBelow=${this.isAndroid10OrBelow}`)
this.localFolders = (await this.$db.getLocalFolders()) || [] this.localFolders = (await this.$db.getLocalFolders()) || []
this.localLibraryItems = await this.$db.getLocalLibraryItems() this.localLibraryItems = await this.$db.getLocalLibraryItems()
} }

View file

@ -272,6 +272,7 @@ export default {
}, },
showItemDialog() { showItemDialog() {
this.selectedAudioTrack = null this.selectedAudioTrack = null
this.selectedEpisode = null
this.showDialog = true this.showDialog = true
}, },
showTrackDialog(track) { showTrackDialog(track) {

View file

@ -2,6 +2,7 @@ import Vue from 'vue'
import vClickOutside from 'v-click-outside' import vClickOutside from 'v-click-outside'
import { App } from '@capacitor/app' import { App } from '@capacitor/app'
import { Dialog } from '@capacitor/dialog' import { Dialog } from '@capacitor/dialog'
import { AbsFileSystem } from '@/plugins/capacitor'
import { StatusBar, Style } from '@capacitor/status-bar'; import { StatusBar, Style } from '@capacitor/status-bar';
import { formatDistance, format, addDays, isDate } from 'date-fns' import { formatDistance, format, addDays, isDate } from 'date-fns'
import { Capacitor } from '@capacitor/core' import { Capacitor } from '@capacitor/core'
@ -17,6 +18,13 @@ if (Capacitor.getPlatform() != 'web') {
Vue.prototype.$isDev = process.env.NODE_ENV !== 'production' Vue.prototype.$isDev = process.env.NODE_ENV !== 'production'
Vue.prototype.$getAndroidSDKVersion = async () => {
if (Capacitor.getPlatform() !== 'android') return null
const data = await AbsFileSystem.getSDKVersion()
if (isNaN(data?.version)) return null
return Number(data.version)
}
Vue.prototype.$encodeUriPath = (path) => { Vue.prototype.$encodeUriPath = (path) => {
return path.replace(/\\/g, '/').replace(/%/g, '%25').replace(/#/g, '%23') return path.replace(/\\/g, '/').replace(/%/g, '%25').replace(/#/g, '%23')
} }

View file

@ -36,6 +36,8 @@ export const state = () => ({
libraryIcons: ['database', 'audiobookshelf', 'books-1', 'books-2', 'book-1', 'microphone-1', 'microphone-3', 'radio', 'podcast', 'rss', 'headphones', 'music', 'file-picture', 'rocket', 'power', 'star', 'heart'], libraryIcons: ['database', 'audiobookshelf', 'books-1', 'books-2', 'book-1', 'microphone-1', 'microphone-3', 'radio', 'podcast', 'rss', 'headphones', 'music', 'file-picture', 'rocket', 'power', 'star', 'heart'],
selectedPlaylistItems: [], selectedPlaylistItems: [],
showPlaylistsAddCreateModal: false, showPlaylistsAddCreateModal: false,
showSelectLocalFolderModal: false,
localFolderSelectData: null,
hapticFeedback: 'LIGHT' hapticFeedback: 'LIGHT'
}) })
@ -181,6 +183,13 @@ export const mutations = {
setShowPlaylistsAddCreateModal(state, val) { setShowPlaylistsAddCreateModal(state, val) {
state.showPlaylistsAddCreateModal = val state.showPlaylistsAddCreateModal = val
}, },
showSelectLocalFolderModal(state, data) {
state.localFolderSelectData = data
state.showSelectLocalFolderModal = true
},
setShowSelectLocalFolderModal(state, val) {
state.showSelectLocalFolderModal = val
},
setHapticFeedback(state, val) { setHapticFeedback(state, val) {
state.hapticFeedback = val || 'LIGHT' state.hapticFeedback = val || 'LIGHT'
} }