Merge pull request #1158 from omnivore-app/feat/ios-beta-offline-audio

Allow beta users to download audio for offline listening
This commit is contained in:
Jackson Harper 2022-09-01 20:40:14 +08:00 committed by GitHub
commit fca1ffcd01
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 72 additions and 7 deletions

View file

@ -137,6 +137,35 @@ import Views
}
}
}
.sheet(isPresented: $viewModel.showAudioInfoAlert) {
VStack {
Text("Welcome to the Omnivore text to speech beta.")
.font(.appTitle)
Spacer()
Text(
"""
This build introduces offline text to speech files. Normally these files will\
be downloaded in the background and made available offline.
During the beta these files can be manually downloaded by long pressing on an item and\
choosing Download Audio, or by tapping the play button. When you first tap the\
play button, the audio will be generated and downloaded. This can take some time.
Future versions will do this in the background.
""")
Text("")
Spacer()
Button(
action: { viewModel.dismissAudioInfoAlert() },
label: { Text("Dismiss").frame(maxWidth: .infinity) }
)
.buttonStyle(RoundedRectButtonStyle())
}.padding(24)
}
.task {
if viewModel.items.isEmpty {
loadItems(isRefresh: true)
@ -223,6 +252,8 @@ import Views
struct HomeFeedListView: View {
@EnvironmentObject var dataService: DataService
@EnvironmentObject var audioSession: AudioSession
@Binding var prefersListLayout: Bool
@State private var itemToRemove: LinkedItem?
@ -276,6 +307,10 @@ import Views
Label { Text("Snooze") } icon: { Image.moon }
}
}
Button(
action: { viewModel.downloadAudio(audioSession: audioSession, item: item) },
label: { Label("Download Audio", systemImage: "icloud.and.arrow.down") }
)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
if !item.isArchived {
@ -360,6 +395,8 @@ import Views
viewModel.itemUnderLabelEdit = item
case .editTitle:
viewModel.itemUnderTitleEdit = item
case .downloadAudio:
viewModel.downloadAudio(audioSession: audioSession, item: item)
}
}

View file

@ -26,6 +26,7 @@ import Views
@Published var linkRequest: LinkRequest?
@Published var showLoadingBar = false
@Published var appliedSort = LinkedItemSort.newest.rawValue
@AppStorage(UserDefaultKey.audioInfoAlertShown.rawValue) var showAudioInfoAlert = false
@AppStorage(UserDefaultKey.lastSelectedLinkedItemFilter.rawValue) var appliedFilter = LinkedItemFilter.inbox.rawValue
@ -55,7 +56,7 @@ import Views
items.insert(item, at: 0)
}
func loadItems(dataService: DataService, audioSession _: AudioSession, isRefresh: Bool) async {
func loadItems(dataService: DataService, audioSession: AudioSession, isRefresh: Bool) async {
let syncStartTime = Date()
let thisSearchIdx = searchIdx
searchIdx += 1
@ -123,7 +124,13 @@ import Views
cursor = queryResult.cursor
if let username = dataService.currentViewer?.username {
await dataService.prefetchPages(itemIDs: newItems.map(\.unwrappedID), username: username)
// await audioSession.preload(itemIDs: newItems.map(\.unwrappedID))
// Only preload the first item in the list. We are doing this during the beta
// because it will kick off the user's future items being automatically transcribed.
// This happens because when an article is saved, we check if the user has a recent
// listen. If they do, we will automatically transcribe their message.
if let first = newItems.first?.id {
_ = await audioSession.preload(itemIDs: [first])
}
}
} else {
updateFetchController(dataService: dataService)
@ -133,6 +140,19 @@ import Views
showLoadingBar = false
}
func dismissAudioInfoAlert() {
UserDefaults.standard.set(true, forKey: UserDefaultKey.audioInfoAlertShown.rawValue)
showAudioInfoAlert = false
}
func downloadAudio(audioSession: AudioSession, item: LinkedItem) {
Snackbar.show(message: "Downloading Offline Audio")
Task {
let downloaded = await audioSession.preload(itemIDs: [item.unwrappedID])
Snackbar.show(message: downloaded ? "Audio file downloaded" : "Error downloading audio")
}
}
private var fetchRequest: NSFetchRequest<Models.LinkedItem> {
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()

View file

@ -76,7 +76,7 @@ public class AudioSession: NSObject, ObservableObject, AVAudioPlayerDelegate {
downloadTask?.cancel()
}
public func preload(itemIDs: [String], retryCount: Int = 0) async {
public func preload(itemIDs: [String], retryCount: Int = 0) async -> Bool {
var pendingList = [String]()
for pageId in itemIDs {
@ -96,23 +96,25 @@ public class AudioSession: NSObject, ObservableObject, AVAudioPlayerDelegate {
if let result = result, result.pending {
print("audio file is pending download: ", pageId)
pendingList.append(pageId)
} else {
print("audio file is downloaded: ", pageId)
}
}
print("audio files pending download: ", pendingList)
if pendingList.isEmpty {
return
return true
}
if retryCount > 5 {
print("reached max preload depth, stopping preloading")
return
return false
}
let retryDelayInNanoSeconds = UInt64(retryCount * 2 * 1_000_000_000)
try? await Task.sleep(nanoseconds: retryDelayInNanoSeconds)
await preload(itemIDs: pendingList, retryCount: retryCount + 1)
return await preload(itemIDs: pendingList, retryCount: retryCount + 1)
}
public var localAudioUrl: URL? {

View file

@ -13,4 +13,5 @@ public enum UserDefaultKey: String {
case lastUsedAppVersion
case lastUsedAppBuildNumber
case lastItemSyncTime
case audioInfoAlertShown
}

View file

@ -7,6 +7,7 @@ public enum GridCardAction {
case delete
case editLabels
case editTitle
case downloadAudio
}
public struct GridCard: View {
@ -65,6 +66,10 @@ public struct GridCard: View {
action: { menuActionHandler(.delete) },
label: { Label("Delete", systemImage: "trash") }
)
Button(
action: { menuActionHandler(.downloadAudio) },
label: { Label("Download Audio", systemImage: "icloud.and.arrow.down") }
)
}
}

View file

@ -27,7 +27,7 @@ public struct Snackbar: View {
HStack {
self.text
.font(.appCallout)
.foregroundColor(self.colorScheme == .light ? .appGrayText : .appTextDefault)
.foregroundColor(self.colorScheme == .light ? .white : .appTextDefault)
Spacer()
}
.padding()