Refactoring the UX for digest view
This commit is contained in:
committed by
Hongbo Wu
parent
d2ea93a7dd
commit
47150c2991
@ -0,0 +1,303 @@
|
||||
import SwiftUI
|
||||
import Models
|
||||
import Services
|
||||
|
||||
public class FullScreenDigestViewModel: ObservableObject {
|
||||
@Published var isLoading = false
|
||||
@Published var digest: DigestResult?
|
||||
|
||||
func load(dataService: DataService) async {
|
||||
isLoading = true
|
||||
if digest == nil {
|
||||
do {
|
||||
digest = try await dataService.getLatestDigest(timeoutInterval: 10)
|
||||
} catch {
|
||||
print("ERROR WITH DIGEST: ", error)
|
||||
}
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
struct DigestAudioItem: AudioItemProperties {
|
||||
let audioItemType = Models.AudioItemType.digest
|
||||
|
||||
var itemID = ""
|
||||
|
||||
var title = "TITLE"
|
||||
|
||||
var byline: String? = "byline"
|
||||
|
||||
var imageURL: URL? = nil
|
||||
|
||||
var language: String?
|
||||
|
||||
var startIndex: Int = 0
|
||||
var startOffset: Double = 0.0
|
||||
}
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
@MainActor
|
||||
struct FullScreenDigestView: View {
|
||||
let viewModel: DigestViewModel = DigestViewModel()
|
||||
let dataService: DataService
|
||||
let audioController: AudioController
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
let textBody = "In a significant political turn, the SOTU response faces unexpected collapse, " +
|
||||
"marking a stark contrast to Trump's latest downturn, alongside an unprecedented " +
|
||||
"surge in Biden's fundraising efforts as of 3/11/24, according to the TDPS Podcast. " +
|
||||
"The analysis provides insights into the shifting dynamics of political support and " +
|
||||
"the potential implications for future electoral strategies. Based on the information " +
|
||||
"you provided, the video seems to discuss a recent event where former President " +
|
||||
"Donald Trump made a controversial statement that shocked even his own audience. " +
|
||||
"The video likely covers Trump's response to the State of the Union (SOTU) address " +
|
||||
"and how it received negative feedback, possibly leading to a decline in his support " +
|
||||
"or approval ratings. Additionally, it appears that the video touches upon a surge " +
|
||||
"in fundraising for President Joe Biden's administration around March 11, 2024."
|
||||
|
||||
public init(dataService: DataService, audioController: AudioController) {
|
||||
self.dataService = dataService
|
||||
self.audioController = audioController
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
// ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top)) {
|
||||
Group {
|
||||
if viewModel.isLoading {
|
||||
ProgressView()
|
||||
} else {
|
||||
itemBody
|
||||
.task {
|
||||
await viewModel.load(dataService: dataService)
|
||||
}.onAppear {
|
||||
self.audioController.play(itemAudioProperties: DigestAudioItem())
|
||||
}
|
||||
}
|
||||
} .navigationTitle("Omnivore digest")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
||||
// HStack(alignment: .top) {
|
||||
// Spacer()
|
||||
// closeButton
|
||||
// }
|
||||
// .padding(20)
|
||||
// }
|
||||
}
|
||||
|
||||
var closeButton: some View {
|
||||
Button(action: {
|
||||
dismiss()
|
||||
}, label: {
|
||||
ZStack {
|
||||
Circle()
|
||||
.foregroundColor(Color.appGrayText)
|
||||
.frame(width: 36, height: 36)
|
||||
.opacity(0.1)
|
||||
|
||||
Image(systemName: "xmark")
|
||||
.font(.appCallout)
|
||||
.frame(width: 36, height: 36)
|
||||
}
|
||||
})
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
var itemBody: some View {
|
||||
VStack {
|
||||
ScrollView(.vertical) {
|
||||
VStack(spacing: 20) {
|
||||
Text("SOTU response collapses, Trump hits new low, Biden fundraising explodes 3/11/24 TDPS Podcast")
|
||||
.font(.title)
|
||||
Text(textBody)
|
||||
.font(.body)
|
||||
}
|
||||
}
|
||||
// .scrollTargetBehavior(.paging)
|
||||
// .ignoresSafeArea()
|
||||
MiniPlayerViewer()
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 40)
|
||||
.background(Color.themeTabBarColor)
|
||||
.onTapGesture {
|
||||
// showExpandedAudioPlayer = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public class PreviewItemViewModel: ObservableObject {
|
||||
let dataService: DataService
|
||||
@Published var item: DigestItem
|
||||
let showSwipeHint: Bool
|
||||
|
||||
@Published var isLoading = false
|
||||
@Published var resultText: String?
|
||||
@Published var promptDisplayText: String?
|
||||
|
||||
init(dataService: DataService, item: DigestItem, showSwipeHint: Bool) {
|
||||
self.dataService = dataService
|
||||
self.item = item
|
||||
self.showSwipeHint = showSwipeHint
|
||||
}
|
||||
|
||||
func loadResult() async {
|
||||
// isLoading = true
|
||||
// let taskId = try? await dataService.createAITask(
|
||||
// extraText: extraText,
|
||||
// libraryItemId: item?.id ?? "",
|
||||
// promptName: "summarize-001"
|
||||
// )
|
||||
//
|
||||
// if let taskId = taskId {
|
||||
// do {
|
||||
// let fetchedText = try await dataService.pollAITask(jobId: taskId, timeoutInterval: 30)
|
||||
// resultText = fetchedText
|
||||
// } catch {
|
||||
// print("ERROR WITH RESULT TEXT: ", error)
|
||||
// }
|
||||
// } else {
|
||||
// print("NO TASK ID: ", taskId)
|
||||
// }
|
||||
// isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
struct PreviewItemView: View {
|
||||
@StateObject var viewModel: PreviewItemViewModel
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 10) {
|
||||
HStack {
|
||||
AsyncImage(url: viewModel.item.siteIcon) { phase in
|
||||
if let image = phase.image {
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
} else {
|
||||
Color.appButtonBackground
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
}
|
||||
}
|
||||
Text(viewModel.item.site)
|
||||
.font(Font.system(size: 14))
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
}
|
||||
.padding(.top, 10)
|
||||
Text(viewModel.item.title)
|
||||
// .font(.body)
|
||||
// .fontWeight(.semibold)
|
||||
.font(Font.system(size: 18, weight: .semibold))
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
|
||||
Text(viewModel.item.author)
|
||||
.font(Font.system(size: 14))
|
||||
.foregroundColor(Color(hex: "898989"))
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
|
||||
Color(hex: "2A2A2A")
|
||||
.frame(height: 1)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.padding(.vertical, 20)
|
||||
|
||||
if viewModel.isLoading {
|
||||
ProgressView()
|
||||
.task {
|
||||
await viewModel.loadResult()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
Text(viewModel.item.summaryText)
|
||||
.font(Font.system(size: 16))
|
||||
// .font(.body)
|
||||
.lineSpacing(12.0)
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
HStack {
|
||||
Button(action: {}, label: {
|
||||
HStack(alignment: .center) {
|
||||
Text("Start listening")
|
||||
.font(Font.system(size: 14))
|
||||
.frame(height: 42, alignment: .center)
|
||||
Image(systemName: "play.fill")
|
||||
.resizable()
|
||||
.frame(width: 10, height: 10)
|
||||
}
|
||||
.padding(.horizontal, 15)
|
||||
.background(Color.blue)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(18)
|
||||
})
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 20)
|
||||
}
|
||||
Spacer()
|
||||
if viewModel.showSwipeHint {
|
||||
VStack {
|
||||
Image.doubleChevronUp
|
||||
Text("Swipe up for next article")
|
||||
.foregroundColor(Color(hex: "898989"))
|
||||
}
|
||||
.padding(.bottom, 50)
|
||||
}
|
||||
}.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.top, 100)
|
||||
.padding(.horizontal, 15)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct RatingView: View {
|
||||
@State private var rating: Int = 0
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 30) {
|
||||
Text("Rate today's digest")
|
||||
.font(.title)
|
||||
.padding(.vertical, 40)
|
||||
Text("I liked the stories picked for today's digest")
|
||||
RatingWidget()
|
||||
|
||||
Text("The stories were interesting")
|
||||
RatingWidget()
|
||||
|
||||
Text("The voices sounded good")
|
||||
RatingWidget()
|
||||
|
||||
Text("I liked the music")
|
||||
RatingWidget()
|
||||
Spacer()
|
||||
}.padding(.top, 60)
|
||||
}
|
||||
}
|
||||
|
||||
struct StarView: View {
|
||||
var isFilled: Bool
|
||||
var body: some View {
|
||||
Image(systemName: isFilled ? "star.fill" : "star")
|
||||
.foregroundColor(isFilled ? Color.yellow : Color.gray)
|
||||
}
|
||||
}
|
||||
|
||||
struct RatingWidget: View {
|
||||
@State private var rating: Int = 0
|
||||
var body: some View {
|
||||
HStack {
|
||||
ForEach(1...5, id: \.self) { index in
|
||||
StarView(isFilled: index <= rating)
|
||||
.onTapGesture {
|
||||
rating = index
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color(hex: "313131"))
|
||||
.cornerRadius(8)
|
||||
// .shadow(radius: 3)
|
||||
}
|
||||
}
|
||||
@ -1,267 +0,0 @@
|
||||
import SwiftUI
|
||||
import Models
|
||||
import Services
|
||||
|
||||
|
||||
struct DigestItem {
|
||||
let id: String
|
||||
let site: String
|
||||
let siteIcon: URL?
|
||||
let author: String
|
||||
let title: String
|
||||
let summaryText: String
|
||||
let keyPointsText: String
|
||||
let highlightsText: String
|
||||
}
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
@MainActor
|
||||
struct LibraryDigestView: View {
|
||||
let dataService: DataService
|
||||
// @State private var currentIndex = 0
|
||||
@State private var items: [DigestItem]
|
||||
// @State private var preloadedItems: [Int: String] = [:]
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
// let itemCount = 10 // Number of items to initially load
|
||||
// let prefetchCount = 2 // Number of items to prefetch
|
||||
|
||||
public init(dataService: DataService) {
|
||||
self.dataService = dataService
|
||||
self.items = [
|
||||
DigestItem(
|
||||
id: "1468AFAA-88sdfsdfC-4546-BE02-EACF385288FC",
|
||||
site: "CNBC.com",
|
||||
siteIcon: URL(string: "https://www.cnbc.com/favicon.ico"),
|
||||
author: "Kif Leswing",
|
||||
title: "Apple shares just had their best day since last May",
|
||||
summaryText: "In a significant political turn, the SOTU response faces unexpected collapse, marking a stark contrast to Trump's latest" +
|
||||
" downturn, alongside an unprecedented surge in Biden's fundraising efforts as of 3/11/24, according to the TDPS Podcast. " +
|
||||
"The analysis provides insights into the shifting dynamics of political support and the potential implications for future " +
|
||||
"electoral strategies. ",
|
||||
keyPointsText: "Key points from the article:",
|
||||
highlightsText: "Highlights from the article:"
|
||||
),
|
||||
DigestItem(
|
||||
id: "1468AFAA-8sdfsdffsdf-4546-BE02-EACF385288FC",
|
||||
site: "CNBC.com",
|
||||
siteIcon: URL(string: "https://www.cnbc.com/favicon.ico"),
|
||||
author: "Kif Leswing",
|
||||
title: "Apple shares just had their best day since last May",
|
||||
summaryText: "In a significant political turn, the SOTU response faces unexpected collapse, marking a stark contrast to Trump's latest" +
|
||||
" downturn, alongside an unprecedented surge in Biden's fundraising efforts as of 3/11/24, according to the TDPS Podcast. " +
|
||||
"The analysis provides insights into the shifting dynamics of political support and the potential implications for future " +
|
||||
"electoral strategies. ",
|
||||
keyPointsText: "Key points from the article:",
|
||||
highlightsText: "Highlights from the article:"
|
||||
),
|
||||
DigestItem(
|
||||
id: "1468AFAA-882C-asdadfsa85288FC",
|
||||
site: "CNBC.com",
|
||||
siteIcon: URL(string: "https://www.cnbc.com/favicon.ico"),
|
||||
author: "Kif Leswing",
|
||||
title: "Apple shares just had their best day since last May",
|
||||
summaryText: "In a significant political turn, the SOTU response faces unexpected collapse, marking a stark contrast to Trump's latest" +
|
||||
" downturn, alongside an unprecedented surge in Biden's fundraising efforts as of 3/11/24, according to the TDPS Podcast. " +
|
||||
"The analysis provides insights into the shifting dynamics of political support and the potential implications for future " +
|
||||
"electoral strategies. ",
|
||||
keyPointsText: "Key points from the article:",
|
||||
highlightsText: "Highlights from the article:"
|
||||
)
|
||||
]
|
||||
// currentIndex = 0
|
||||
// _preloadedItems = [Int:String]
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
itemBody
|
||||
}
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
var itemBody: some View {
|
||||
ScrollView(.vertical) {
|
||||
LazyVStack(spacing: 0) {
|
||||
ForEach(Array(self.items.enumerated()), id: \.1.id) { idx, item in
|
||||
PreviewItemView(
|
||||
viewModel: PreviewItemViewModel(dataService: dataService, item: item, showSwipeHint: idx == 0)
|
||||
)
|
||||
.containerRelativeFrame([.horizontal, .vertical])
|
||||
}
|
||||
}
|
||||
.scrollTargetLayout()
|
||||
}
|
||||
.scrollTargetBehavior(.paging)
|
||||
.ignoresSafeArea()
|
||||
|
||||
|
||||
// ScrollView(.horizontal, showsIndicators: false) {
|
||||
// HStack(spacing: 0) {
|
||||
// ForEach(items.indices, id: \.self) { index in
|
||||
// if let item = preloadedItems[index] {
|
||||
// ItemView(content: item)
|
||||
// .onAppear {
|
||||
// if index == items.count - prefetchCount {
|
||||
// fetchItems()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.onAppear {
|
||||
// fetchItems()
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in
|
||||
// Pause any background tasks if necessary
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// private func fetchItems() {
|
||||
// // Simulate fetching items from an API
|
||||
// for idx in currentIndex..<currentIndex + itemCount {
|
||||
// fetchItem(index: idx)
|
||||
// }
|
||||
// currentIndex += itemCount
|
||||
// }
|
||||
//
|
||||
// private func fetchItem(index: Int) {
|
||||
// // Simulate fetching item content from an API
|
||||
// DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
// items.append("Item \(index + 1)")
|
||||
// preloadNextItemIfNeeded(index: index)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private func preloadNextItemIfNeeded(index: Int) {
|
||||
// let nextIndex = index + 1
|
||||
// if nextIndex < currentIndex + prefetchCount {
|
||||
// fetchItem(index: nextIndex)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public class PreviewItemViewModel: ObservableObject {
|
||||
let dataService: DataService
|
||||
@Published var item: DigestItem
|
||||
let showSwipeHint: Bool
|
||||
|
||||
@Published var isLoading = false
|
||||
@Published var resultText: String?
|
||||
@Published var promptDisplayText: String?
|
||||
|
||||
init(dataService: DataService, item: DigestItem, showSwipeHint: Bool) {
|
||||
self.dataService = dataService
|
||||
self.item = item
|
||||
self.showSwipeHint = showSwipeHint
|
||||
}
|
||||
|
||||
func loadResult() async {
|
||||
// isLoading = true
|
||||
// let taskId = try? await dataService.createAITask(
|
||||
// extraText: extraText,
|
||||
// libraryItemId: item?.id ?? "",
|
||||
// promptName: "summarize-001"
|
||||
// )
|
||||
//
|
||||
// if let taskId = taskId {
|
||||
// do {
|
||||
// let fetchedText = try await dataService.pollAITask(jobId: taskId, timeoutInterval: 30)
|
||||
// resultText = fetchedText
|
||||
// } catch {
|
||||
// print("ERROR WITH RESULT TEXT: ", error)
|
||||
// }
|
||||
// } else {
|
||||
// print("NO TASK ID: ", taskId)
|
||||
// }
|
||||
// isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
struct PreviewItemView: View {
|
||||
@StateObject var viewModel: PreviewItemViewModel
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 10) {
|
||||
HStack {
|
||||
AsyncImage(url: viewModel.item.siteIcon) { phase in
|
||||
if let image = phase.image {
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
} else {
|
||||
Color.appButtonBackground
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
}
|
||||
}
|
||||
Text(viewModel.item.site)
|
||||
.font(Font.system(size: 14))
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
}
|
||||
.padding(.top, 10)
|
||||
Text(viewModel.item.title)
|
||||
// .font(.body)
|
||||
// .fontWeight(.semibold)
|
||||
.font(Font.system(size: 18, weight: .semibold))
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
|
||||
Text(viewModel.item.author)
|
||||
.font(Font.system(size: 14))
|
||||
.foregroundColor(Color(hex: "898989"))
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
|
||||
Color(hex: "2A2A2A")
|
||||
.frame(height: 1)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.padding(.vertical, 20)
|
||||
|
||||
if viewModel.isLoading {
|
||||
ProgressView()
|
||||
.task {
|
||||
await viewModel.loadResult()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
Text(viewModel.item.summaryText)
|
||||
.font(Font.system(size: 16))
|
||||
// .font(.body)
|
||||
.lineSpacing(12.0)
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
HStack {
|
||||
Button(action: {}, label: {
|
||||
HStack(alignment: .center) {
|
||||
Text("Start listening")
|
||||
.font(Font.system(size: 14))
|
||||
.frame(height: 42, alignment: .center)
|
||||
Image(systemName: "play.fill")
|
||||
.resizable()
|
||||
.frame(width: 10, height: 10)
|
||||
}
|
||||
.padding(.horizontal, 15)
|
||||
.background(Color.blue)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(18)
|
||||
})
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 20)
|
||||
}
|
||||
Spacer()
|
||||
if viewModel.showSwipeHint {
|
||||
VStack {
|
||||
Image.doubleChevronUp
|
||||
Text("Swipe up for next article")
|
||||
.foregroundColor(Color(hex: "898989"))
|
||||
}
|
||||
.padding(.bottom, 50)
|
||||
}
|
||||
}.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.top, 100)
|
||||
.padding(.horizontal, 15)
|
||||
|
||||
}
|
||||
}
|
||||
@ -45,7 +45,7 @@
|
||||
if audioController.playbackError {
|
||||
return AnyView(Color.clear)
|
||||
}
|
||||
if let itemID = audioController.itemAudioProperties?.itemID, audioController.isLoadingItem(itemID: itemID) {
|
||||
if audioController.isLoadingItem(audioController.itemAudioProperties) {
|
||||
return AnyView(ProgressView())
|
||||
} else {
|
||||
return AnyView(Button(
|
||||
@ -98,11 +98,11 @@
|
||||
}
|
||||
).padding(.trailing, 5)
|
||||
|
||||
if !(audioController.itemAudioProperties?.isArchived ?? false) {
|
||||
if !((audioController.itemAudioProperties as? LinkedItemAudioProperties)?.isArchived ?? false) {
|
||||
Button(
|
||||
action: { performArchive() },
|
||||
label: {
|
||||
if audioController.itemAudioProperties?.isArchived ?? false {
|
||||
if (audioController.itemAudioProperties as? LinkedItemAudioProperties)?.isArchived ?? false {
|
||||
Image
|
||||
.toolbarUnarchive
|
||||
.foregroundColor(Color.toolbarItemForeground)
|
||||
@ -130,20 +130,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
func performViewArticle() {
|
||||
if let objectID = audioController.itemAudioProperties?.objectID {
|
||||
viewArticle(objectID)
|
||||
}
|
||||
}
|
||||
// func performViewArticle() {
|
||||
// if let objectID = audioController.itemAudioProperties?.objectID {
|
||||
// viewArticle(objectID)
|
||||
// }
|
||||
// }
|
||||
|
||||
func performDelete() {
|
||||
if let objectID = audioController.itemAudioProperties?.objectID {
|
||||
if let objectID = (audioController.itemAudioProperties as? LinkedItemAudioProperties)?.objectID {
|
||||
delete(objectID)
|
||||
}
|
||||
}
|
||||
|
||||
func performArchive() {
|
||||
if let objectID = audioController.itemAudioProperties?.objectID {
|
||||
if let objectID = (audioController.itemAudioProperties as? LinkedItemAudioProperties)?.objectID {
|
||||
archive(objectID)
|
||||
}
|
||||
}
|
||||
@ -467,7 +467,7 @@
|
||||
}
|
||||
|
||||
public var innerBody: some View {
|
||||
if let itemAudioProperties = self.audioController.itemAudioProperties {
|
||||
if let itemAudioProperties = self.audioController.itemAudioProperties as? LinkedItemAudioProperties {
|
||||
return AnyView(
|
||||
playerContent(itemAudioProperties)
|
||||
.tint(.appGrayTextContrast)
|
||||
|
||||
@ -23,14 +23,14 @@
|
||||
public var body: some View {
|
||||
ZStack(alignment: .center) {
|
||||
presentingView
|
||||
if let itemAudioProperties = self.audioController.itemAudioProperties {
|
||||
if self.audioController.itemAudioProperties != nil {
|
||||
ZStack(alignment: .bottom) {
|
||||
Color.systemBackground.edgesIgnoringSafeArea(.bottom)
|
||||
.frame(height: expanded ? 0 : 110, alignment: .bottom)
|
||||
|
||||
VStack {
|
||||
Spacer(minLength: 0)
|
||||
MiniPlayerViewer(itemAudioProperties: itemAudioProperties)
|
||||
MiniPlayerViewer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,8 +13,6 @@
|
||||
|
||||
@State var expanded = true
|
||||
|
||||
let itemAudioProperties: LinkedItemAudioProperties
|
||||
|
||||
var playPauseButtonImage: String {
|
||||
switch audioController.state {
|
||||
case .playing:
|
||||
@ -32,7 +30,7 @@
|
||||
if audioController.playbackError {
|
||||
return AnyView(Color.clear)
|
||||
}
|
||||
if let itemID = audioController.itemAudioProperties?.itemID, audioController.isLoadingItem(itemID: itemID) {
|
||||
if audioController.isLoadingItem(audioController.itemAudioProperties) {
|
||||
return AnyView(ProgressView())
|
||||
} else {
|
||||
return AnyView(Button(
|
||||
@ -84,8 +82,8 @@
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
|
||||
func artwork(_ itemAudioProperties: LinkedItemAudioProperties, forDimensions dim: Double) -> some View {
|
||||
if let imageURL = itemAudioProperties.imageURL {
|
||||
func artwork(_ itemAudioProperties: AudioItemProperties?, forDimensions dim: Double) -> some View {
|
||||
if let imageURL = itemAudioProperties?.imageURL {
|
||||
return AnyView(AsyncImage(url: imageURL) { phase in
|
||||
if let image = phase.image {
|
||||
image
|
||||
@ -125,9 +123,9 @@
|
||||
Text("There was an error playing back your audio.").foregroundColor(Color.red).font(.footnote)
|
||||
Spacer(minLength: 0)
|
||||
} else {
|
||||
artwork(itemAudioProperties, forDimensions: 50)
|
||||
artwork(audioController.itemAudioProperties, forDimensions: 50)
|
||||
|
||||
Text(itemAudioProperties.title)
|
||||
Text(audioController.itemAudioProperties?.title ?? "")
|
||||
.font(Font.system(size: 17, weight: .medium))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.lineLimit(2)
|
||||
|
||||
@ -204,6 +204,9 @@ struct AnimatingCellHeight: AnimatableModifier {
|
||||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
@State private var selection = Set<String>()
|
||||
|
||||
@AppStorage("LibraryList::digestEnabled") var digestEnabled = false
|
||||
@AppStorage("LibraryList::hasCheckedForDigestFeature") var hasCheckedForDigestFeature = false
|
||||
|
||||
init(viewModel: HomeFeedViewModel, isEditMode: Binding<EditMode>) {
|
||||
_viewModel = ObservedObject(wrappedValue: viewModel)
|
||||
_isEditMode = isEditMode
|
||||
@ -225,7 +228,7 @@ struct AnimatingCellHeight: AnimatableModifier {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
ZStack {
|
||||
HomeFeedView(
|
||||
listTitle: $listTitle,
|
||||
isListScrolled: $isListScrolled,
|
||||
@ -265,8 +268,8 @@ struct AnimatingCellHeight: AnimatableModifier {
|
||||
VStack(spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
if let audioProperties = audioController.itemAudioProperties {
|
||||
MiniPlayerViewer(itemAudioProperties: audioProperties)
|
||||
if audioController.itemAudioProperties != nil {
|
||||
MiniPlayerViewer()
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 20)
|
||||
.background(Color.themeTabBarColor)
|
||||
@ -317,6 +320,15 @@ struct AnimatingCellHeight: AnimatableModifier {
|
||||
}
|
||||
)
|
||||
}
|
||||
.fullScreenCover(isPresented: $showLibraryDigest) {
|
||||
if #available(iOS 17.0, *) {
|
||||
NavigationView {
|
||||
FullScreenDigestView(dataService: dataService, audioController: audioController)
|
||||
}
|
||||
} else {
|
||||
Text("Sorry digest is only available on iOS 17 and above")
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
toolbarItems
|
||||
}
|
||||
@ -339,6 +351,20 @@ struct AnimatingCellHeight: AnimatableModifier {
|
||||
viewModel.stopUsingFollowingPrimer = true
|
||||
}
|
||||
}
|
||||
.task {
|
||||
do {
|
||||
if let viewer = try await dataService.fetchViewer() {
|
||||
digestEnabled = viewer.digestEnabled ?? false
|
||||
if !hasCheckedForDigestFeature {
|
||||
hasCheckedForDigestFeature = true
|
||||
// selectedTab = "digest"
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("ERROR FETCHING VIEWER: ", error)
|
||||
print("")
|
||||
}
|
||||
}
|
||||
.environment(\.editMode, self.$isEditMode)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
@ -383,15 +409,14 @@ struct AnimatingCellHeight: AnimatableModifier {
|
||||
if isEditMode == .active {
|
||||
Button(action: { isEditMode = .inactive }, label: { Text("Cancel") })
|
||||
} else {
|
||||
|
||||
// if #available(iOS 17.0, *) {
|
||||
// Button(
|
||||
// action: { showLibraryDigest = true },
|
||||
// label: { Image(systemName: "sparkles") }
|
||||
// )
|
||||
// .buttonStyle(.plain)
|
||||
// .padding(.trailing, 4)
|
||||
// }
|
||||
if #available(iOS 17.0, *) {
|
||||
Button(
|
||||
action: { showLibraryDigest = true },
|
||||
label: { Image.tabDigestSelected }
|
||||
)
|
||||
.buttonStyle(.plain)
|
||||
.padding(.trailing, 4)
|
||||
}
|
||||
if prefersListLayout {
|
||||
Button(
|
||||
action: { isEditMode = isEditMode == .active ? .inactive : .active },
|
||||
@ -481,12 +506,12 @@ struct AnimatingCellHeight: AnimatableModifier {
|
||||
let showFeatureCards: Bool
|
||||
var slideTransition: PresentationLinkTransition {
|
||||
PresentationLinkTransition.slide(
|
||||
options: PresentationLinkTransition.SlideTransitionOptions(edge: .trailing,
|
||||
options:
|
||||
PresentationLinkTransition.Options(
|
||||
modalPresentationCapturesStatusBarAppearance: true
|
||||
)
|
||||
))
|
||||
options: PresentationLinkTransition.SlideTransitionOptions(
|
||||
edge: .trailing,
|
||||
options: PresentationLinkTransition.Options(
|
||||
modalPresentationCapturesStatusBarAppearance: true
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@ -494,12 +519,12 @@ struct AnimatingCellHeight: AnimatableModifier {
|
||||
if let linkRequest = viewModel.linkRequest, viewModel.currentListConfig?.hasReadNowSection ?? false {
|
||||
PresentationLink(
|
||||
transition: PresentationLinkTransition.slide(
|
||||
options: PresentationLinkTransition.SlideTransitionOptions(edge: .trailing,
|
||||
options:
|
||||
PresentationLinkTransition.Options(
|
||||
modalPresentationCapturesStatusBarAppearance: true,
|
||||
preferredPresentationBackgroundColor: ThemeManager.currentBgColor
|
||||
))),
|
||||
options: PresentationLinkTransition.SlideTransitionOptions(
|
||||
edge: .trailing,
|
||||
options: PresentationLinkTransition.Options(
|
||||
modalPresentationCapturesStatusBarAppearance: true,
|
||||
preferredPresentationBackgroundColor: ThemeManager.currentBgColor
|
||||
))),
|
||||
isPresented: $viewModel.presentWebContainer,
|
||||
destination: {
|
||||
WebReaderLoadingContainer(requestID: linkRequest.serverID)
|
||||
|
||||
@ -1,10 +1,3 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Jackson Harper on 6/29/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Models
|
||||
import Services
|
||||
@ -21,9 +14,6 @@ struct LibraryTabView: View {
|
||||
@AppStorage("LibraryTabView::hideFollowingTab") var hideFollowingTab = false
|
||||
@AppStorage(UserDefaultKey.lastSelectedTabItem.rawValue) var selectedTab = "inbox"
|
||||
|
||||
@AppStorage("LibraryTabView::digestEnabled") var digestEnabled = false
|
||||
@AppStorage("LibraryTabView::hasCheckedForDigestFeature") var hasCheckedForDigestFeature = false
|
||||
|
||||
@State var isEditMode: EditMode = .inactive
|
||||
@State var showExpandedAudioPlayer = false
|
||||
@State var presentPushContainer = true
|
||||
@ -79,6 +69,8 @@ struct LibraryTabView: View {
|
||||
@State var operationStatus: OperationStatus = .none
|
||||
@State var operationMessage: String?
|
||||
|
||||
@State var digestEnabled = false
|
||||
|
||||
var showDigest: Bool {
|
||||
if digestEnabled, #available(iOS 17.0, *) {
|
||||
return true
|
||||
@ -143,7 +135,7 @@ struct LibraryTabView: View {
|
||||
|
||||
if showDigest, #available(iOS 17.0, *) {
|
||||
NavigationView {
|
||||
LibraryDigestView(dataService: dataService)
|
||||
DigestView(dataService: dataService)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationViewStyle(.stack)
|
||||
}.tag("digest")
|
||||
@ -165,8 +157,8 @@ struct LibraryTabView: View {
|
||||
}
|
||||
|
||||
}
|
||||
if let audioProperties = audioController.itemAudioProperties {
|
||||
MiniPlayerViewer(itemAudioProperties: audioProperties)
|
||||
if audioController.itemAudioProperties != nil {
|
||||
MiniPlayerViewer()
|
||||
.onTapGesture {
|
||||
showExpandedAudioPlayer = true
|
||||
}
|
||||
@ -236,19 +228,5 @@ struct LibraryTabView: View {
|
||||
}
|
||||
selectedTab = "inbox"
|
||||
}
|
||||
.task {
|
||||
do {
|
||||
if let viewer = try await dataService.fetchViewer() {
|
||||
digestEnabled = viewer.digestEnabled ?? false
|
||||
if !hasCheckedForDigestFeature {
|
||||
hasCheckedForDigestFeature = true
|
||||
selectedTab = "digest"
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("ERROR FETCHING VIEWER: ", error)
|
||||
print("")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ import Transmission
|
||||
return AnyView(splitView)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
func startTimer(amount: Int) {
|
||||
self.snackbarTimer = Timer.scheduledTimer(withTimeInterval: TimeInterval(amount / 1000), repeats: false) { _ in
|
||||
DispatchQueue.main.async {
|
||||
|
||||
@ -133,7 +133,7 @@ struct WebReaderContainerView: View {
|
||||
|
||||
#if os(iOS)
|
||||
var audioNavbarItem: some View {
|
||||
if !audioController.playbackError, audioController.isLoadingItem(itemID: item.unwrappedID) {
|
||||
if audioController.isLoadingItem(audioController.itemAudioProperties) {
|
||||
return AnyView(ProgressView()
|
||||
.padding(.horizontal))
|
||||
} else {
|
||||
@ -500,20 +500,6 @@ struct WebReaderContainerView: View {
|
||||
.formSheet(isPresented: $showOpenArchiveSheet) {
|
||||
OpenArchiveTodayView(item: item)
|
||||
}
|
||||
.formSheet(isPresented: $showExplainSheet) {
|
||||
ExplainView(
|
||||
viewModel: ExplainViewModel(
|
||||
dataService: dataService,
|
||||
item: self.item,
|
||||
promptName: "explain-text-001",
|
||||
extraText: viewModel.explainText
|
||||
),
|
||||
dismissAction: {
|
||||
viewModel.explainText = nil
|
||||
showExplainSheet = false
|
||||
}
|
||||
)
|
||||
}
|
||||
#endif
|
||||
.sheet(isPresented: $showHighlightAnnotationModal) {
|
||||
NavigationView {
|
||||
@ -629,8 +615,8 @@ struct WebReaderContainerView: View {
|
||||
.offset(y: navBarVisible ? 0 : -150)
|
||||
|
||||
Spacer()
|
||||
if let audioProperties = audioController.itemAudioProperties {
|
||||
MiniPlayerViewer(itemAudioProperties: audioProperties)
|
||||
if audioController.itemAudioProperties != nil {
|
||||
MiniPlayerViewer()
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, showBottomBar ? 10 : 40)
|
||||
.background(Color.themeTabBarColor)
|
||||
|
||||
Reference in New Issue
Block a user