Update tab bar to allow opting into digest

This commit is contained in:
Jackson Harper
2024-04-15 20:27:36 +08:00
committed by Hongbo Wu
parent 4e61b360d7
commit 2b53d7791a
8 changed files with 3526 additions and 18 deletions

View File

@ -2,25 +2,31 @@ import Foundation
import SwiftUI import SwiftUI
struct CustomTabBar: View { struct CustomTabBar: View {
let displayTabs: [String]
@Binding var selectedTab: String @Binding var selectedTab: String
let hideFollowingTab: Bool
var body: some View { var body: some View {
HStack(spacing: 0) { HStack(spacing: 0) {
if !hideFollowingTab { if displayTabs.contains("following") {
TabBarButton(key: "following", TabBarButton(key: "following",
image: Image.tabFollowing, image: Image.tabFollowing,
selectedTab: $selectedTab, selectedTab: $selectedTab,
selectionColor: Color(hex: "EE8232")) selectionColor: Color(hex: "EE8232"))
} }
TabBarButton(key: "digest", if displayTabs.contains("digest") {
image: Image.tabDigest, TabBarButton(key: "digest",
selectedTab: $selectedTab, image: Image.tabDigest,
selectedImage: Image.tabDigestSelected) selectedTab: $selectedTab,
TabBarButton(key: "inbox", selectedImage: Image.tabDigestSelected)
image: Image.tabLibrary, }
selectedTab: $selectedTab) if displayTabs.contains("inbox") {
// TabBarButton(key: "profile", image: Image.tabProfile, selectedTab: $selectedTab) TabBarButton(key: "inbox",
image: Image.tabLibrary,
selectedTab: $selectedTab)
}
if displayTabs.contains("profile") {
TabBarButton(key: "profile", image: Image.tabProfile, selectedTab: $selectedTab)
}
} }
.padding(.top, 10) .padding(.top, 10)
.padding(.bottom, 10) .padding(.bottom, 10)

View File

@ -21,6 +21,9 @@ struct LibraryTabView: View {
@AppStorage("LibraryTabView::hideFollowingTab") var hideFollowingTab = false @AppStorage("LibraryTabView::hideFollowingTab") var hideFollowingTab = false
@AppStorage(UserDefaultKey.lastSelectedTabItem.rawValue) var selectedTab = "inbox" @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 isEditMode: EditMode = .inactive
@State var showExpandedAudioPlayer = false @State var showExpandedAudioPlayer = false
@State var presentPushContainer = true @State var presentPushContainer = true
@ -76,6 +79,28 @@ struct LibraryTabView: View {
@State var operationStatus: OperationStatus = .none @State var operationStatus: OperationStatus = .none
@State var operationMessage: String? @State var operationMessage: String?
var showDigest: Bool {
if digestEnabled, #available(iOS 17.0, *) {
return true
}
return false
}
var displayTabs: [String] {
var res = [String]()
if !hideFollowingTab {
res.append("following")
}
if showDigest {
res.append("digest")
}
res.append("inbox")
if !showDigest {
res.append("profile")
}
return res
}
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
WindowLink(level: .alert, transition: .move(edge: .bottom), isPresented: $showOperationToast) { WindowLink(level: .alert, transition: .move(edge: .bottom), isPresented: $showOperationToast) {
@ -116,24 +141,29 @@ struct LibraryTabView: View {
}.tag("following") }.tag("following")
} }
if #available(iOS 17.0, *) { if showDigest, #available(iOS 17.0, *) {
NavigationView { NavigationView {
LibraryDigestView(dataService: dataService) LibraryDigestView(dataService: dataService)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(.stack) .navigationViewStyle(.stack)
}.tag("digest") }.tag("digest")
NavigationView {
HomeFeedContainerView(viewModel: inboxViewModel, isEditMode: $isEditMode)
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(.stack)
}.tag("inbox")
} else { } else {
NavigationView { NavigationView {
HomeFeedContainerView(viewModel: inboxViewModel, isEditMode: $isEditMode) HomeFeedContainerView(viewModel: inboxViewModel, isEditMode: $isEditMode)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(.stack) .navigationViewStyle(.stack)
}.tag("inbox") }.tag("inbox")
NavigationView {
ProfileView()
.navigationViewStyle(.stack)
}.tag("profile")
} }
NavigationView {
ProfileView()
.navigationViewStyle(.stack)
}.tag("profile")
} }
if let audioProperties = audioController.itemAudioProperties { if let audioProperties = audioController.itemAudioProperties {
MiniPlayerViewer(itemAudioProperties: audioProperties) MiniPlayerViewer(itemAudioProperties: audioProperties)
@ -146,7 +176,9 @@ struct LibraryTabView: View {
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
} }
if isEditMode != .active { if isEditMode != .active {
CustomTabBar(selectedTab: $selectedTab, hideFollowingTab: hideFollowingTab) CustomTabBar(
displayTabs: displayTabs,
selectedTab: $selectedTab)
.padding(0) .padding(0)
} }
} }
@ -204,5 +236,19 @@ struct LibraryTabView: View {
} }
selectedTab = "inbox" 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("")
}
}
} }
} }

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23B81" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier=""> <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22757" systemVersion="23B81" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Filter" representedClassName="Filter" syncable="YES" codeGenerationType="class"> <entity name="Filter" representedClassName="Filter" syncable="YES" codeGenerationType="class">
<attribute name="defaultFilter" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> <attribute name="defaultFilter" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="filter" optional="YES" attributeType="String"/> <attribute name="filter" optional="YES" attributeType="String"/>
@ -137,6 +137,7 @@
<attribute name="username" optional="YES" attributeType="String"/> <attribute name="username" optional="YES" attributeType="String"/>
</entity> </entity>
<entity name="Viewer" representedClassName="Viewer" syncable="YES" codeGenerationType="class"> <entity name="Viewer" representedClassName="Viewer" syncable="YES" codeGenerationType="class">
<attribute name="digestEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="name" attributeType="String"/> <attribute name="name" attributeType="String"/>
<attribute name="profileImageURL" optional="YES" attributeType="String"/> <attribute name="profileImageURL" optional="YES" attributeType="String"/>
<attribute name="userID" attributeType="String"/> <attribute name="userID" attributeType="String"/>

View File

@ -0,0 +1,10 @@
public struct FeatureInternal {
public let name: String
public let enabled: Bool
public init(name: String, enabled: Bool) {
self.name = name
self.enabled = enabled
}
}

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,8 @@ public enum RuleActionType {
case delete case delete
case markAsRead case markAsRead
case sendNotification case sendNotification
case export
case webhook
static func from(_ other: Enums.RuleActionType) -> RuleActionType { static func from(_ other: Enums.RuleActionType) -> RuleActionType {
switch other { switch other {
@ -28,6 +30,10 @@ public enum RuleActionType {
return .sendNotification return .sendNotification
case .delete: case .delete:
return .delete return .delete
case Enums.RuleActionType.export:
return .export
case Enums.RuleActionType.webhook:
return .webhook
} }
} }
} }

View File

@ -17,7 +17,9 @@ public extension DataService {
profileImageURL: try $0.profile( profileImageURL: try $0.profile(
selection: .init { try $0.pictureUrl() } selection: .init { try $0.pictureUrl() }
), ),
intercomHash: try $0.intercomHash() intercomHash: try $0.intercomHash(),
digestEnabled: true // (try $0.featureList(selection: featureSelection.list.nullable)?
// .filter { $0.enabled && $0.name == "digest" } ?? []).count > 0
) )
} }
@ -65,6 +67,7 @@ public struct ViewerInternal {
public let name: String public let name: String
public let profileImageURL: String? public let profileImageURL: String?
public let intercomHash: String? public let intercomHash: String?
public let digestEnabled: Bool?
func persist(context: NSManagedObjectContext) throws { func persist(context: NSManagedObjectContext) throws {
try context.performAndWait { try context.performAndWait {
@ -73,6 +76,7 @@ public struct ViewerInternal {
viewer.username = username viewer.username = username
viewer.name = name viewer.name = name
viewer.profileImageURL = profileImageURL viewer.profileImageURL = profileImageURL
viewer.digestEnabled = digestEnabled ?? false
do { do {
try context.save() try context.save()

View File

@ -0,0 +1,6 @@
import Models
import SwiftGraphQL
let featureSelection = Selection.Feature {
FeatureInternal(name: try $0.name(), enabled: try $0.grantedAt() != nil)
}