diff --git a/apple/OmnivoreKit/Sources/App/Views/BriefingView.swift b/apple/OmnivoreKit/Sources/App/Views/BriefingView.swift index 4843f995c..6c781c48d 100644 --- a/apple/OmnivoreKit/Sources/App/Views/BriefingView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/BriefingView.swift @@ -82,7 +82,7 @@ struct BriefingView: View { var body: some View { ZStack { // Using ZStack so .task can be used on if/else body if let item = viewModel.item { - WebReaderContainerView(item: item) + WebReaderContainerView(item: item, pop: {}) } } .task { diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift b/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift index 15c55a6fb..8eeb2e4e6 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift @@ -12,24 +12,21 @@ struct MacFeedCardNavigationLink: View { @ObservedObject var viewModel: HomeFeedViewModel var body: some View { - ZStack { - NavigationLink( - destination: LinkItemDetailView( - linkedItemObjectID: item.objectID, - isPDF: item.isPDF - ), - tag: item.objectID, - selection: $viewModel.selectedLinkItem - ) { - EmptyView() - } - .opacity(0) - .buttonStyle(PlainButtonStyle()) - .onAppear { - Task { await viewModel.itemAppeared(item: item, dataService: dataService) } - } + NavigationLink( + destination: LinkItemDetailView( + linkedItemObjectID: item.objectID, + isPDF: item.isPDF + ), + tag: item.objectID, + selection: $viewModel.selectedLinkItem + ) { LibraryItemCard(item: item, viewer: dataService.currentViewer) } + .opacity(0) + .buttonStyle(PlainButtonStyle()) + .onAppear { + Task { await viewModel.itemAppeared(item: item, dataService: dataService) } + } } } @@ -43,20 +40,16 @@ struct FeedCardNavigationLink: View { var body: some View { ZStack { - Button { - viewModel.selectedItem = item - viewModel.linkIsActive = true - } label: { - NavigationLink(destination: EmptyView()) { - EmptyView() - } - .opacity(0) - .buttonStyle(PlainButtonStyle()) - .onAppear { - Task { await viewModel.itemAppeared(item: item, dataService: dataService) } - } - LibraryItemCard(item: item, viewer: dataService.currentViewer) - } + LibraryItemCard(item: item, viewer: dataService.currentViewer) + NavigationLink(destination: LinkItemDetailView( + linkedItemObjectID: item.objectID, + isPDF: item.isPDF + ), label: { + EmptyView() + }).opacity(0) + } + .onAppear { + Task { await viewModel.itemAppeared(item: item, dataService: dataService) } } } } @@ -75,28 +68,16 @@ struct GridCardNavigationLink: View { @ObservedObject var viewModel: HomeFeedViewModel var body: some View { - ZStack { - Button { - if isContextMenuOpen { - isContextMenuOpen = false - } else { - viewModel.selectedItem = item - viewModel.linkIsActive = true - } - } label: { - NavigationLink(destination: EmptyView()) { - EmptyView() - } - .opacity(0) - .buttonStyle(PlainButtonStyle()) - .onAppear { - Task { await viewModel.itemAppeared(item: item, dataService: dataService) } - } - GridCard(item: item, isContextMenuOpen: $isContextMenuOpen, actionHandler: actionHandler) - } + NavigationLink(destination: LinkItemDetailView( + linkedItemObjectID: item.objectID, + isPDF: item.isPDF + )) { + GridCard(item: item, isContextMenuOpen: $isContextMenuOpen, actionHandler: actionHandler) } - .aspectRatio(1.8, contentMode: .fill) - .scaleEffect(scale) + .onAppear { + Task { await viewModel.itemAppeared(item: item, dataService: dataService) } + } + .aspectRatio(1.0, contentMode: .fill) .background( Color.secondarySystemGroupedBackground .onTapGesture { diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/Components/LibraryFeatureCardNavigationLink.swift b/apple/OmnivoreKit/Sources/App/Views/Home/Components/LibraryFeatureCardNavigationLink.swift index 05c9d4b50..78a8c7409 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/Components/LibraryFeatureCardNavigationLink.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/Components/LibraryFeatureCardNavigationLink.swift @@ -20,49 +20,41 @@ struct LibraryFeatureCardNavigationLink: View { @State var showFeatureActions = false var body: some View { - ZStack { - Button { + NavigationLink(destination: LinkItemDetailView( + linkedItemObjectID: item.objectID, + isPDF: item.isPDF + )) { + LibraryFeatureCard(item: item, viewer: dataService.currentViewer) + } + .confirmationDialog("", isPresented: $showFeatureActions) { + if FeaturedItemFilter(rawValue: viewModel.featureFilter) == .pinned { + Button("Unpin", action: { + viewModel.unpinItem(dataService: dataService, item: item) + }) + } + Button("Pin", action: { + viewModel.pinItem(dataService: dataService, item: item) + }) + Button("Archive", action: { + viewModel.setLinkArchived(dataService: dataService, objectID: item.objectID, archived: true) + }) + Button("Remove", action: { + viewModel.removeLink(dataService: dataService, objectID: item.objectID) + }) + if FeaturedItemFilter(rawValue: viewModel.featureFilter) != .pinned { + Button("Mark Read", action: { + viewModel.markRead(dataService: dataService, item: item) + }) + Button("Mark Unread", action: { + viewModel.markUnread(dataService: dataService, item: item) + }) + } + Button("Dismiss", role: .cancel, action: { showFeatureActions = false - viewModel.selectedItem = item - viewModel.linkIsActive = true - } label: { - NavigationLink(destination: EmptyView()) { - EmptyView() - } - .opacity(0) - .buttonStyle(PlainButtonStyle()) - LibraryFeatureCard(item: item, viewer: dataService.currentViewer) - } - .confirmationDialog("", isPresented: $showFeatureActions) { - if FeaturedItemFilter(rawValue: viewModel.featureFilter) == .pinned { - Button("Unpin", action: { - viewModel.unpinItem(dataService: dataService, item: item) - }) - } - Button("Pin", action: { - viewModel.pinItem(dataService: dataService, item: item) - }) - Button("Archive", action: { - viewModel.setLinkArchived(dataService: dataService, objectID: item.objectID, archived: true) - }) - Button("Remove", action: { - viewModel.removeLink(dataService: dataService, objectID: item.objectID) - }) - if FeaturedItemFilter(rawValue: viewModel.featureFilter) != .pinned { - Button("Mark Read", action: { - viewModel.markRead(dataService: dataService, item: item) - }) - Button("Mark Unread", action: { - viewModel.markUnread(dataService: dataService, item: item) - }) - } - Button("Dismiss", role: .cancel, action: { - showFeatureActions = false - }) - } - .delayedGesture(LongPressGesture().onEnded { _ in - showFeatureActions = true }) } + .delayedGesture(LongPressGesture().onEnded { _ in + showFeatureActions = true + }) } } diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index de560ae6a..dba244997 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -44,121 +44,110 @@ struct AnimatingCellHeight: AnimatableModifier { } var body: some View { - ZStack { - if let linkRequest = viewModel.linkRequest { - NavigationLink( - destination: WebReaderLoadingContainer(requestID: linkRequest.serverID), - tag: linkRequest, - selection: $viewModel.linkRequest - ) { + HomeFeedView( + listTitle: $listTitle, + isListScrolled: $isListScrolled, + prefersListLayout: $prefersListLayout, + viewModel: viewModel + ) + .refreshable { + loadItems(isRefresh: true) + } + .onChange(of: viewModel.searchTerm) { _ in + // Maybe we should debounce this, but + // it feels like it works ok without + loadItems(isRefresh: true) + } + .onChange(of: viewModel.selectedLabels) { _ in + loadItems(isRefresh: true) + } + .onChange(of: viewModel.negatedLabels) { _ in + loadItems(isRefresh: true) + } + .onChange(of: viewModel.appliedFilter) { _ in + loadItems(isRefresh: true) + } + .onChange(of: viewModel.appliedSort) { _ in + loadItems(isRefresh: true) + } + .sheet(item: $viewModel.itemUnderLabelEdit) { item in + ApplyLabelsView(mode: .item(item), onSave: nil) + } + .sheet(item: $viewModel.itemUnderTitleEdit) { item in + LinkedItemMetadataEditView(item: item) + } + .sheet(item: $viewModel.itemForHighlightsView) { item in + NotebookView(itemObjectID: item.objectID, hasHighlightMutations: $hasHighlightMutations) + } + .sheet(isPresented: $viewModel.showFiltersModal) { + NavigationView { + FilterSelectorView(viewModel: viewModel) + } + } + // .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .barLeading) { + VStack(alignment: .leading) { + let title = (LinkedItemFilter(rawValue: viewModel.appliedFilter) ?? LinkedItemFilter.inbox).displayName + + Text(title) + .font(Font.system(size: isListScrolled ? 10 : 18, weight: .semibold)) + + if prefersListLayout, isListScrolled { + Text(listTitle) + .font(Font.system(size: 15, weight: .regular)) + .foregroundColor(Color.appGrayText) + } + }.frame(maxWidth: .infinity, alignment: .leading) + } + ToolbarItem(placement: .barTrailing) { + Button("", action: {}) + .disabled(true) + .overlay { + if viewModel.isLoading, !prefersListLayout, enableGrid { + ProgressView() + } + } + } + ToolbarItem(placement: UIDevice.isIPhone ? .barLeading : .barTrailing) { + if enableGrid { + Button( + action: { prefersListLayout.toggle() }, + label: { + Label("Toggle Feed Layout", systemImage: prefersListLayout ? "square.grid.2x2" : "list.bullet") + } + ) + } else { EmptyView() } } - HomeFeedView( - listTitle: $listTitle, - isListScrolled: $isListScrolled, - prefersListLayout: $prefersListLayout, - viewModel: viewModel - ) - .refreshable { - loadItems(isRefresh: true) - } - .onChange(of: viewModel.searchTerm) { _ in - // Maybe we should debounce this, but - // it feels like it works ok without - loadItems(isRefresh: true) - } - .onChange(of: viewModel.selectedLabels) { _ in - loadItems(isRefresh: true) - } - .onChange(of: viewModel.negatedLabels) { _ in - loadItems(isRefresh: true) - } - .onChange(of: viewModel.appliedFilter) { _ in - loadItems(isRefresh: true) - } - .onChange(of: viewModel.appliedSort) { _ in - loadItems(isRefresh: true) - } - .sheet(item: $viewModel.itemUnderLabelEdit) { item in - ApplyLabelsView(mode: .item(item), onSave: nil) - } - .sheet(item: $viewModel.itemUnderTitleEdit) { item in - LinkedItemMetadataEditView(item: item) - } - .sheet(item: $viewModel.itemForHighlightsView) { item in - NotebookView(itemObjectID: item.objectID, hasHighlightMutations: $hasHighlightMutations) - } - .sheet(isPresented: $viewModel.showFiltersModal) { - NavigationView { - FilterSelectorView(viewModel: viewModel) - } - } - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .barLeading) { - VStack(alignment: .leading) { - let title = (LinkedItemFilter(rawValue: viewModel.appliedFilter) ?? LinkedItemFilter.inbox).displayName - - Text(title) - .font(Font.system(size: isListScrolled ? 10 : 18, weight: .semibold)) - - if isListScrolled { - Text(listTitle) - .font(Font.system(size: 15, weight: .regular)) - .foregroundColor(Color.appGrayText) - } - }.frame(maxWidth: .infinity, alignment: .leading) - } - ToolbarItem(placement: .barTrailing) { - Button("", action: {}) - .disabled(true) - .overlay { - if viewModel.isLoading, !prefersListLayout, enableGrid { - ProgressView() - } - } - } - ToolbarItem(placement: UIDevice.isIPhone ? .barLeading : .barTrailing) { - if enableGrid { - Button( - action: { prefersListLayout.toggle() }, - label: { - Label("Toggle Feed Layout", systemImage: prefersListLayout ? "square.grid.2x2" : "list.bullet") - } - ) - } else { - EmptyView() - } - } - ToolbarItem(placement: .barTrailing) { - Button( - action: { searchPresented = true }, - label: { - Image(systemName: "magnifyingglass") - .resizable() - .frame(width: 18, height: 18) - .padding(.vertical) - .foregroundColor(.appGrayTextContrast) - } - ) - } - ToolbarItem(placement: .barTrailing) { - if UIDevice.isIPhone { - Menu(content: { - Button(action: { settingsPresented = true }, label: { - Label(LocalText.genericProfile, systemImage: "person.circle") - }) - Button(action: { addLinkPresented = true }, label: { - Label("Add Link", systemImage: "plus.circle") - }) - }, label: { - Image.utilityMenu - }) + ToolbarItem(placement: .barTrailing) { + Button( + action: { searchPresented = true }, + label: { + Image(systemName: "magnifyingglass") + .resizable() + .frame(width: 18, height: 18) + .padding(.vertical) .foregroundColor(.appGrayTextContrast) - } else { - EmptyView() } + ) + } + ToolbarItem(placement: .barTrailing) { + if UIDevice.isIPhone { + Menu(content: { + Button(action: { settingsPresented = true }, label: { + Label(LocalText.genericProfile, systemImage: "person.circle") + }) + Button(action: { addLinkPresented = true }, label: { + Label("Add Link", systemImage: "plus.circle") + }) + }, label: { + Image.utilityMenu + }) + .foregroundColor(.appGrayTextContrast) + } else { + EmptyView() } } } @@ -173,11 +162,6 @@ struct AnimatingCellHeight: AnimatableModifier { viewModel.selectedItem = linkedItem viewModel.linkIsActive = true } - .onReceive(NSNotification.pushReaderItemPublisher) { notification in - if let objectID = notification.userInfo?["objectID"] as? NSManagedObjectID { - viewModel.handleReaderItemNotification(objectID: objectID, dataService: dataService) - } - } .onOpenURL { url in viewModel.linkRequest = nil if let deepLink = DeepLink.make(from: url) { @@ -244,10 +228,23 @@ struct AnimatingCellHeight: AnimatableModifier { var body: some View { VStack(spacing: 0) { + if let linkRequest = viewModel.linkRequest { + NavigationLink( + destination: WebReaderLoadingContainer(requestID: linkRequest.serverID), + tag: linkRequest, + selection: $viewModel.linkRequest + ) { + EmptyView() + } + } + NavigationLink(destination: LinkDestination(selectedItem: viewModel.selectedItem), isActive: $viewModel.linkIsActive) { + EmptyView() + } + if prefersListLayout || !enableGrid { HomeFeedListView(listTitle: $listTitle, isListScrolled: $isListScrolled, prefersListLayout: $prefersListLayout, viewModel: viewModel) } else { - HomeFeedGridView(viewModel: viewModel) + HomeFeedGridView(viewModel: viewModel, isListScrolled: $isListScrolled) } }.sheet(isPresented: $viewModel.showLabelsSheet) { FilterByLabelsView( @@ -312,7 +309,8 @@ struct AnimatingCellHeight: AnimatableModifier { }, label: { TextChipButton.makeMenuButton( - title: LinkedItemFilter(rawValue: viewModel.appliedFilter)?.displayName ?? "Filter" + title: LinkedItemFilter(rawValue: viewModel.appliedFilter)?.displayName ?? "Filter", + color: .systemGray6 ) } ) @@ -325,13 +323,12 @@ struct AnimatingCellHeight: AnimatableModifier { }, label: { TextChipButton.makeMenuButton( - title: LinkedItemSort(rawValue: viewModel.appliedSort)?.displayName ?? "Sort" + title: LinkedItemSort(rawValue: viewModel.appliedSort)?.displayName ?? "Sort", + color: .systemGray6 ) } ) - TextChipButton.makeAddLabelButton { - viewModel.showLabelsSheet = true - } + TextChipButton.makeAddLabelButton(color: .systemGray6, onTap: { viewModel.showLabelsSheet = true }) ForEach(viewModel.selectedLabels, id: \.self) { label in TextChipButton.makeRemovableLabelButton(feedItemLabel: label, negated: false) { viewModel.selectedLabels.removeAll { $0.id == label.id } @@ -636,6 +633,7 @@ struct AnimatingCellHeight: AnimatableModifier { @State var isContextMenuOpen = false @ObservedObject var viewModel: HomeFeedViewModel + @Binding var isListScrolled: Bool func contextMenuActionHandler(item: LinkedItem, action: GridCardAction) { switch action { @@ -673,7 +671,8 @@ struct AnimatingCellHeight: AnimatableModifier { }, label: { TextChipButton.makeMenuButton( - title: LinkedItemFilter(rawValue: viewModel.appliedFilter)?.displayName ?? "Filter" + title: LinkedItemFilter(rawValue: viewModel.appliedFilter)?.displayName ?? "Filter", + color: .systemGray6 ) } ) @@ -686,13 +685,12 @@ struct AnimatingCellHeight: AnimatableModifier { }, label: { TextChipButton.makeMenuButton( - title: LinkedItemSort(rawValue: viewModel.appliedSort)?.displayName ?? "Sort" + title: LinkedItemSort(rawValue: viewModel.appliedSort)?.displayName ?? "Sort", + color: .systemGray6 ) } ) - TextChipButton.makeAddLabelButton { - viewModel.showLabelsSheet = true - } + TextChipButton.makeAddLabelButton(color: .systemGray6, onTap: { viewModel.showLabelsSheet = true }) ForEach(viewModel.selectedLabels, id: \.self) { label in TextChipButton.makeRemovableLabelButton(feedItemLabel: label, negated: false) { viewModel.selectedLabels.removeAll { $0.id == label.id } @@ -709,16 +707,33 @@ struct AnimatingCellHeight: AnimatableModifier { } .listRowSeparator(.hidden) } + .dynamicTypeSize(.small ... .accessibility1) } var body: some View { - ZStack { - ScrollView { - filtersHeader - .padding(.leading, 16) - .padding(.bottom, 25) + VStack(alignment: .leading) { + if viewModel.showLoadingBar { + ShimmeringLoader() + } else { + Spacer(minLength: 2) + } - LazyVGrid(columns: [GridItem(.adaptive(minimum: 325), spacing: 16)], alignment: .leading, spacing: 16) { + filtersHeader + .onAppear { + withAnimation { + isListScrolled = false + } + } + .onDisappear { + withAnimation { + isListScrolled = true + } + } + .padding(.horizontal, 20) + .frame(maxHeight: 35) + + ScrollView { + LazyVGrid(columns: [GridItem(.adaptive(minimum: 325, maximum: 400), spacing: 16)], alignment: .center, spacing: 30) { ForEach(viewModel.items) { item in GridCardNavigationLink( item: item, @@ -726,9 +741,6 @@ struct AnimatingCellHeight: AnimatableModifier { isContextMenuOpen: $isContextMenuOpen, viewModel: viewModel ) - .contextMenu { - libraryItemMenu(dataService: dataService, viewModel: viewModel, item: item) - } } Spacer() } @@ -754,6 +766,9 @@ struct AnimatingCellHeight: AnimatableModifier { LoadingSection() } } + .background(Color(.systemGroupedBackground)) + + Spacer() } } } diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 1dd059b5c..d309dcc3a 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -79,35 +79,6 @@ import Views } } - func handleReaderItemNotification(objectID: NSManagedObjectID, dataService: DataService) { - // Pop the current selected item if needed - if selectedItem != nil, selectedItem?.objectID != objectID { - // Temporarily disable animation to avoid excessive animations - #if os(iOS) - UIView.setAnimationsEnabled(false) - #endif - - linkIsActive = false - selectedItem = nil - - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { - self.selectedLinkItem = objectID - self.selectedItem = dataService.viewContext.object(with: objectID) as? LinkedItem - self.linkIsActive = true - } - - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { - #if os(iOS) - UIView.setAnimationsEnabled(true) - #endif - } - } else { - selectedLinkItem = objectID - selectedItem = dataService.viewContext.object(with: objectID) as? LinkedItem - linkIsActive = true - } - } - func itemAppeared(item: LinkedItem, dataService: DataService) async { if isLoading { return } let itemIndex = items.firstIndex(where: { $0.id == item.id }) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/LibraryListView.swift b/apple/OmnivoreKit/Sources/App/Views/Home/LibraryListView.swift index 8fdcd89d5..bbf94692b 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/LibraryListView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/LibraryListView.swift @@ -38,23 +38,21 @@ struct LibraryListView: View { ) var body: some View { - ZStack { - NavigationLink( - destination: LinkDestination(selectedItem: libraryViewModel.selectedItem), - isActive: $libraryViewModel.linkIsActive - ) { - EmptyView() - } - HomeView(viewModel: libraryViewModel) - .tabItem { - Label { - Text("Library") - } icon: { - Image.tabLibrary - } +// ZStack { +// NavigationLink( +// destination: LinkDestination(selectedItem: libraryViewModel.selectedItem), +// isActive: $libraryViewModel.linkIsActive +// ) { +// EmptyView() +// } + HomeView(viewModel: libraryViewModel) + .tabItem { + Label { + Text("Library") + } icon: { + Image.tabLibrary } - } - .navigationViewStyle(.stack) - .navigationBarTitleDisplayMode(.inline) + } +// } } } diff --git a/apple/OmnivoreKit/Sources/App/Views/LibraryTabView.swift b/apple/OmnivoreKit/Sources/App/Views/LibraryTabView.swift index 42c684c73..9d3347b7a 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LibraryTabView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LibraryTabView.swift @@ -43,45 +43,14 @@ struct LibraryTabView: View { ) var body: some View { - NavigationView { - ZStack { - NavigationLink( - destination: LinkDestination(selectedItem: libraryViewModel.selectedItem), - isActive: $libraryViewModel.linkIsActive - ) { - EmptyView() - } - // TabView(selection: $selection) { - // BriefingView( - // articleId: "98e017a3-79d5-4049-97bc-ff170153792a" - // ) - // .tabItem { - // Label { - // Text("Your Briefing") - // } icon: { - // Image.tabBriefing.padding(.trailing, 5) - // } - // }.tag(0) - + if #available(iOS 16.0, *) { + NavigationView { HomeView(viewModel: libraryViewModel) - // .tabItem { - // Label { - // Text("Library") - // } icon: { - // Image.tabLibrary - // } - // }.tag(1) - - // HomeView(viewModel: highlightsViewModel) - // .tabItem { - // Label { - // Text("Highlights") - // } icon: { - // Image.tabHighlights - // } - // }.tag(2) - // } + .navigationBarHidden(false) } + } else { + // Fallback on earlier versions + EmptyView() } } } diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift index e2504bb47..83422da7f 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift @@ -75,25 +75,29 @@ struct LinkItemDetailView: View { @StateObject private var viewModel = LinkItemDetailViewModel() + @State var isEnabled = true + @Environment(\.dismiss) var dismiss + init(linkedItemObjectID: NSManagedObjectID, isPDF: Bool) { self.linkedItemObjectID = linkedItemObjectID self.isPDF = isPDF } var body: some View { - ZStack { // Using ZStack so .task can be used on if/else body + ZStack { if isPDF { pdfContainerView } else if let item = viewModel.item { - WebReaderContainerView(item: item) + WebReaderContainerView(item: item, pop: { dismiss() }) + .navigationBarHidden(true) + .lazyPop(pop: { + dismiss() + }, isEnabled: $isEnabled) } } .task { await viewModel.loadItem(linkedItemObjectID: linkedItemObjectID, dataService: dataService) } - #if os(iOS) - .navigationBarHidden(true) - #endif } @ViewBuilder private var pdfContainerView: some View { @@ -113,17 +117,3 @@ struct LinkItemDetailView: View { } } } - -#if os(iOS) - // Enable swipe to go back behavior if nav bar is hidden - extension UINavigationController: UIGestureRecognizerDelegate { - override open func viewDidLoad() { - super.viewDidLoad() - interactivePopGestureRecognizer?.delegate = self - } - - public func gestureRecognizerShouldBegin(_: UIGestureRecognizer) -> Bool { - viewControllers.count > 1 - } - } -#endif diff --git a/apple/OmnivoreKit/Sources/App/Views/PrimaryContentView.swift b/apple/OmnivoreKit/Sources/App/Views/PrimaryContentView.swift index c00315237..483664bd0 100644 --- a/apple/OmnivoreKit/Sources/App/Views/PrimaryContentView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/PrimaryContentView.swift @@ -19,6 +19,8 @@ import Views return AnyView(splitView) } else { return AnyView(LibraryTabView()) + // .navigationViewStyle(.stack) + // .navigationBarTitleDisplayMode(.inline) } #else return AnyView(splitView) @@ -44,6 +46,7 @@ import Views // Second column is the Primary Nav Stack PrimaryContentCategory.feed.destinationView } + .navigationBarTitleDisplayMode(.inline) .accentColor(.appGrayTextContrast) .introspectSplitViewController { $0.preferredSplitBehavior = .tile diff --git a/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift b/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift index 19384d98b..292810f96 100644 --- a/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift @@ -49,22 +49,22 @@ struct InnerRootView: View { @ViewBuilder private var innerBody: some View { if authenticator.isLoggedIn { - GeometryReader { geo in - PrimaryContentView() - #if os(iOS) - .miniPlayer() - .formSheet(isPresented: $viewModel.showNewFeaturePrimer, - modalSize: CGSize(width: geo.size.width * 0.66, height: geo.size.width * 0.66)) { - FeaturePrimer.recommendationsPrimer - } - .onAppear { - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) { - viewModel.showNewFeaturePrimer = viewModel.shouldShowNewFeaturePrimer - viewModel.shouldShowNewFeaturePrimer = false - } - } - #endif - } + // GeometryReader { geo in + PrimaryContentView() +// #if os(iOS) +// .miniPlayer() +// .formSheet(isPresented: $viewModel.showNewFeaturePrimer, +// modalSize: CGSize(width: geo.size.width * 0.66, height: geo.size.width * 0.66)) { +// FeaturePrimer.recommendationsPrimer +// } +// .onAppear { +// DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) { +// viewModel.showNewFeaturePrimer = viewModel.shouldShowNewFeaturePrimer +// viewModel.shouldShowNewFeaturePrimer = false +// } +// } +// #endif +// } } else { WelcomeView() .accessibilityElement() diff --git a/apple/OmnivoreKit/Sources/App/Views/SlideAnimatedTransitioning.swift b/apple/OmnivoreKit/Sources/App/Views/SlideAnimatedTransitioning.swift new file mode 100644 index 000000000..1236c513c --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/SlideAnimatedTransitioning.swift @@ -0,0 +1,63 @@ +// +// SlideAnimatedTransitioning.swift +// SwipeRightToPopController +// +// Created by Warif Akhand Rishi on 2/19/16. +// Copyright © 2016 Warif Akhand Rishi. All rights reserved. +// + +import UIKit + +class SlideAnimatedTransitioning: NSObject {} + +extension SlideAnimatedTransitioning: UIViewControllerAnimatedTransitioning { + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + let containerView = transitionContext.containerView + guard + let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from), + let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) + else { + return + } + + let width = containerView.frame.width + + var offsetLeft = fromVC.view?.frame + offsetLeft?.origin.x = width + + var offscreenRight = fromVC.view?.frame + offscreenRight?.origin.x = -width / 3.33 + + toVC.view?.frame = offscreenRight! + + fromVC.view?.layer.shadowRadius = 5.0 + fromVC.view?.layer.shadowOpacity = 1.0 + toVC.view?.layer.opacity = 0.9 + + transitionContext.containerView.addSubview(toVC.view) + transitionContext.containerView.addSubview(fromVC.view) + + UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: .curveLinear, animations: { + toVC.view?.frame = (fromVC.view?.frame)! + fromVC.view?.frame = offsetLeft! + + toVC.view?.layer.opacity = 1.0 + fromVC.view?.layer.shadowOpacity = 0.1 + + }, completion: { _ in + toVC.view?.layer.opacity = 1.0 + toVC.view?.layer.shadowOpacity = 0 + fromVC.view?.layer.opacity = 1.0 + fromVC.view?.layer.shadowOpacity = 0 + + fromVC.view.removeFromSuperview() + // when cancelling or completing the animation, ios simulator seems to sometimes flash black backgrounds during the animation. on devices, this doesn't seem to happen though. + // containerView.backgroundColor = [UIColor whiteColor]; + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + }) + } + + func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval { + 0.3 + } +} diff --git a/apple/OmnivoreKit/Sources/App/Views/SwipeRightToPopViewController.swift b/apple/OmnivoreKit/Sources/App/Views/SwipeRightToPopViewController.swift new file mode 100644 index 000000000..3a2fb967b --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/SwipeRightToPopViewController.swift @@ -0,0 +1,187 @@ +// +// SwipeRightToPopViewController.swift +// SwipeRightToPopController +// +// Created by Warif Akhand Rishi on 2/19/16. +// Copyright © 2016 Warif Akhand Rishi. All rights reserved. +// +// Modified by Joseph Hinkle on 12/1/19. +// Modified version allows use in SwiftUI by subclassing UIHostingController. +// Copyright © 2019 Joseph Hinkle. All rights reserved. +// + +import SwiftUI +private func < (lhs: T?, rhs: T?) -> Bool { + switch (lhs, rhs) { + case let (lll?, rrr?): + return lll < rrr + case (nil, _?): + return true + default: + return false + } +} + +private func > (lhs: T?, rhs: T?) -> Bool { + switch (lhs, rhs) { + case let (lll?, rrr?): + return lll > rrr + default: + return rhs < lhs + } +} + +class SwipeRightToPopViewController: UIHostingController, UINavigationControllerDelegate where Content: View { + fileprivate var pop: (() -> Void)? + fileprivate var lazyPopContent: LazyPop? + private var percentDrivenInteractiveTransition: UIPercentDrivenInteractiveTransition? + private var panGestureRecognizer: UIPanGestureRecognizer! + private var parentNavigationControllerToUse: UINavigationController? + private var gestureAdded = false + + override func viewDidLayoutSubviews() { + // You need to add gesture events after every subview layout to protect against weird edge cases + // One notable edgecase is if you are in a splitview in landscape. In this case, there will be + // no nav controller with 2 vcs, so our addGesture will fail. After rotating back to portrait, + // the splitview will combine into one view with the details pushed on top. So only then would + // would the addGesture find a parent nav controller with 2 view controllers. I don't know if + // there are other edge cases, but running addGesture on every viewDidLayoutSubviews seems safe. + addGesture() + } + + public func addGesture() { + if !gestureAdded { + // attempt to find a parent navigationController + var currentVc: UIViewController = self + while true { + if currentVc.navigationController != nil, + currentVc.navigationController?.viewControllers.count > 1 + { + parentNavigationControllerToUse = currentVc.navigationController + break + } + guard let parent = currentVc.parent else { + return + } + currentVc = parent + } + guard parentNavigationControllerToUse?.viewControllers.count > 1 else { + return + } + + panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(SwipeRightToPopViewController.handlePanGesture(_:))) + view.addGestureRecognizer(panGestureRecognizer) + gestureAdded = true + } + } + + @objc func handlePanGesture(_ panGesture: UIPanGestureRecognizer) { + // if the parentNavigationControllerToUse has a width value, use that because it's more accurate. Otherwise use this view's width as a backup + let total = parentNavigationControllerToUse?.view.frame.width ?? view.frame.width + let percent = max(panGesture.translation(in: view).x, 0) / total + + switch panGesture.state { + case .began: + if lazyPopContent?.isEnabled == true { + parentNavigationControllerToUse?.delegate = self + if let pop = self.pop { + pop() + } + } + + case .changed: + if let percentDrivenInteractiveTransition = percentDrivenInteractiveTransition { + percentDrivenInteractiveTransition.update(percent) + } + + case .ended: + let velocity = panGesture.velocity(in: view).x + + // Continue if drag more than 50% of screen width or velocity is higher than 100 + if percent > 0.5 || velocity > 100 { + percentDrivenInteractiveTransition?.finish() + } else { + percentDrivenInteractiveTransition?.cancel() + } + + case .cancelled, .failed: + percentDrivenInteractiveTransition?.cancel() + + default: + break + } + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + } + + func navigationController(_: UINavigationController, + animationControllerFor _: UINavigationController.Operation, + from _: UIViewController, + to _: UIViewController) -> UIViewControllerAnimatedTransitioning? + { + if #available(iOS 17.0, *) { + return nil + } else if UIDevice.isIPad { + return nil + } else { + return SlideAnimatedTransitioning() + } + } + + func navigationController(_: UINavigationController, + interactionControllerFor _: UIViewControllerAnimatedTransitioning) + -> UIViewControllerInteractiveTransitioning? + { + parentNavigationControllerToUse?.delegate = nil + // navigationController.delegate = nil + + if panGestureRecognizer.state == .began { + percentDrivenInteractiveTransition = UIPercentDrivenInteractiveTransition() + percentDrivenInteractiveTransition?.completionCurve = .easeOut + } else { + percentDrivenInteractiveTransition = nil + } + + return percentDrivenInteractiveTransition + } +} + +// +// Lazy Pop SwiftUI Component +// +// Created by Joseph Hinkle on 12/1/19. +// Copyright © 2019 Joseph Hinkle. All rights reserved. +// + +private struct LazyPop: UIViewControllerRepresentable { + let rootView: Content + let pop: () -> Void + @Binding var isEnabled: Bool + + init(_ rootView: Content, pop: @escaping () -> Void, isEnabled: (Binding)? = nil) { + self.rootView = rootView + self.pop = pop + self._isEnabled = isEnabled ?? Binding(get: { true }, set: { _ in }) + } + + func makeUIViewController(context _: Context) -> UIViewController { + let vc = SwipeRightToPopViewController(rootView: rootView) + vc.pop = pop + vc.lazyPopContent = self + return vc + } + + func updateUIViewController(_ uiViewController: UIViewController, context _: Context) { + if let host = uiViewController as? UIHostingController { + host.rootView = rootView + } + } +} + +public extension View { + func lazyPop(pop: @escaping () -> Void, isEnabled: (Binding)? = nil) -> some View { + LazyPop(self, pop: pop, isEnabled: isEnabled) + } +} diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index 3edc2e333..d6e6528fc 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -10,6 +10,7 @@ import WebKit // swiftlint:disable file_length type_body_length struct WebReaderContainerView: View { let item: LinkedItem + let pop: () -> Void @State private var showPreferencesPopover = false @State private var showPreferencesFormsheet = false @@ -40,9 +41,11 @@ struct WebReaderContainerView: View { @EnvironmentObject var dataService: DataService @EnvironmentObject var audioController: AudioController - @Environment(\.presentationMode) var presentationMode: Binding @Environment(\.openURL) var openURL @StateObject var viewModel = WebReaderViewModel() + @Environment(\.dismiss) var dismiss + + @AppStorage(UserDefaultKey.prefersHideStatusBarInReader.rawValue) var prefersHideStatusBarInReader = false func webViewActionHandler(message: WKScriptMessage, replyHandler: WKScriptMessageReplyHandler?) { if let replyHandler = replyHandler { @@ -271,7 +274,9 @@ struct WebReaderContainerView: View { HStack(alignment: .center, spacing: 10) { #if os(iOS) Button( - action: { self.presentationMode.wrappedValue.dismiss() }, + action: { + pop() + }, label: { Image.chevronRight .padding(.horizontal, 10) @@ -422,6 +427,7 @@ struct WebReaderContainerView: View { showHighlightAnnotationModal: $showHighlightAnnotationModal ) .background(ThemeManager.currentBgColor) + .statusBar(hidden: prefersHideStatusBarInReader) .onAppear { if item.isUnread { dataService.updateLinkReadingProgress(itemID: item.unwrappedID, readingProgress: 0.1, anchorIndex: 0) @@ -620,7 +626,7 @@ struct WebReaderContainerView: View { func archive() { dataService.archiveLink(objectID: item.objectID, archived: !item.isArchived) #if os(iOS) - presentationMode.wrappedValue.dismiss() + pop() #endif } @@ -651,7 +657,7 @@ struct WebReaderContainerView: View { removeLibraryItemAction(dataService: dataService, objectID: item.objectID) #if os(iOS) DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { - presentationMode.wrappedValue.dismiss() + pop() } #endif } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift index fb7ce6101..545968187 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift @@ -34,8 +34,11 @@ import Views public struct WebReaderLoadingContainer: View { let requestID: String + @Environment(\.dismiss) private var dismiss @EnvironmentObject var dataService: DataService @EnvironmentObject var audioController: AudioController + + @State var lazyPopIsEnabled = true @StateObject var viewModel = WebReaderLoadingContainerViewModel() public var body: some View { @@ -53,10 +56,11 @@ public struct WebReaderLoadingContainer: View { } #endif } else { - WebReaderContainerView(item: item) + WebReaderContainerView(item: item, pop: { dismiss() }) #if os(iOS) - .navigationBarHidden(true) .navigationViewStyle(.stack) + .navigationBarHidden(true) + .lazyPop(pop: { dismiss() }, isEnabled: $lazyPopIsEnabled) #endif .accentColor(.appGrayTextContrast) .task { viewModel.trackReadEvent() } diff --git a/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift b/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift index 9eceb0453..1e732bf8c 100644 --- a/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift +++ b/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift @@ -33,4 +33,5 @@ public enum UserDefaultKey: String { case userWordsPerMinute case hideFeatureSection case justifyText + case prefersHideStatusBarInReader } diff --git a/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_labelBackground.colorset/Contents.json b/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_labelBackground.colorset/Contents.json index 61575f6d4..30506bb70 100644 --- a/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_labelBackground.colorset/Contents.json +++ b/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/_labelBackground.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0xF9", - "green" : "0xF9", - "red" : "0xF9" + "blue" : "0xF5", + "green" : "0xF5", + "red" : "0xF5" } }, "idiom" : "universal" diff --git a/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/featureSeparator.colorset/Contents.json b/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/featureSeparator.colorset/Contents.json new file mode 100644 index 000000000..7cb481608 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Colors/ThemeColors.xcassets/featureSeparator.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF7", + "green" : "0xF2", + "red" : "0xF2" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF7", + "green" : "0xF2", + "red" : "0xF2" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/File.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/Gradient.swift similarity index 100% rename from apple/OmnivoreKit/Sources/Views/FeedItem/File.swift rename to apple/OmnivoreKit/Sources/Views/FeedItem/Gradient.swift diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift index e6b6fe9d5..a8652b8c5 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift @@ -69,20 +69,67 @@ public struct GridCard: View { } } + var imageBox: some View { + GeometryReader { geo in + + ZStack(alignment: .bottomLeading) { + if let imageURL = item.imageURL { + AsyncImage(url: imageURL) { phase in + switch phase { + case .empty: + Color.systemBackground + .frame(maxWidth: .infinity, maxHeight: geo.size.height) + case let .success(image): + image.resizable() + .resizable() + .scaledToFill() + .frame(maxWidth: .infinity, maxHeight: geo.size.height) + .clipped() + case .failure: + fallbackImage + + @unknown default: + // Since the AsyncImagePhase enum isn't frozen, + // we need to add this currently unused fallback + // to handle any new cases that might be added + // in the future: + Color.systemBackground + .frame(maxWidth: .infinity, maxHeight: geo.size.height) + } + } + } else { + fallbackImage + } + Color(hex: "#D9D9D9")?.opacity(0.65).frame(width: geo.size.width, height: 5) + Color(hex: "#FFD234").frame(width: geo.size.width * (item.readingProgress / 100), height: 5) + } + } + .cornerRadius(5) + } + + var fallbackImage: some View { + GeometryReader { geo in + HStack { + Text(item.unwrappedTitle.prefix(1)) + .font(Font.system(size: 128, weight: .bold)) + .offset(CGSize(width: -48, height: 12)) + .frame(alignment: .bottomLeading) + .foregroundColor(Gradient.randomColor(str: item.unwrappedTitle, offset: 1)) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Gradient.randomColor(str: item.unwrappedTitle, offset: 0)) + .background(LinearGradient(gradient: Gradient(fromStr: item.unwrappedTitle)!, startPoint: .top, endPoint: .bottom)) + .frame(width: geo.size.width, height: geo.size.height) + } + } + public var body: some View { GeometryReader { geo in VStack(alignment: .leading, spacing: 0) { - // Progress Bar - Group { - ProgressView(value: min(abs(item.readingProgress) / 100, 1)) - .tint(.appYellow48) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.bottom, 16) - } - // .onTapGesture { tapHandler() } - VStack { - // Title, Subtitle, Menu Button + imageBox + .frame(height: geo.size.height / 2.0) + VStack(alignment: .leading, spacing: 4) { HStack { Text(item.unwrappedTitle) @@ -108,42 +155,23 @@ public struct GridCard: View { Spacer() } - // .onTapGesture { tapHandler() } } .frame(height: 30) - .padding(.horizontal) - .padding(.bottom, 16) + .padding(.horizontal, 10) + .padding(.bottom, 10) + .padding(.top, 10) // Link description and image HStack(alignment: .top) { Text(item.descriptionText ?? item.unwrappedTitle) .font(.appSubheadline) .foregroundColor(.appGrayTextContrast) - .lineLimit(nil) + .lineLimit(3) .multilineTextAlignment(.leading) Spacer() - - if let imageURL = item.imageURL { - AsyncImage(url: imageURL) { phase in - if let image = phase.image { - image - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: geo.size.width / 3, height: (geo.size.width * 2) / 9) - .cornerRadius(3) - } else if phase.error != nil { - EmptyView() - } else { - Color.appButtonBackground - .frame(width: geo.size.width / 3, height: (geo.size.width * 2) / 9) - .cornerRadius(3) - } - } - } } - .padding(.horizontal) - // .onTapGesture { tapHandler() } + .padding(.horizontal, 10) // Category Labels if item.hasLabels { @@ -154,18 +182,12 @@ public struct GridCard: View { } Spacer() } - .padding(.horizontal) + .padding(.horizontal, 10) } - // .onTapGesture { tapHandler() } - } - - if item.serverSyncStatus != ServerSyncStatus.isNSync.rawValue { - SyncStatusIcon(status: ServerSyncStatus(rawValue: Int(item.serverSyncStatus)) ?? ServerSyncStatus.isNSync) } } .padding(.horizontal, 0) .padding(.top, 0) - .padding(.bottom, 8) } .contextMenu { contextMenuView } } diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemCard.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemCard.swift index e4cb66b27..eeecbb448 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemCard.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemCard.swift @@ -36,7 +36,8 @@ public struct LibraryItemCard: View { labels } } - .padding(.bottom, 5) + .padding(5) + .padding(.top, 10) .draggableItem(item: item) .dynamicTypeSize(.xSmall ... .accessibility1) } @@ -149,25 +150,42 @@ public struct LibraryItemCard: View { } var imageBox: some View { - Group { + ZStack(alignment: .bottomLeading) { if let imageURL = item.imageURL { AsyncImage(url: imageURL) { phase in if let image = phase.image { image .resizable() .aspectRatio(contentMode: .fill) - .frame(width: 40, height: 40) + .frame(width: 50, height: 75) .cornerRadius(5) .padding(.top, 2) } else { Color.systemBackground - .frame(width: 40, height: 40) + .frame(width: 50, height: 75) .cornerRadius(5) .padding(.top, 2) } } + } else { + fallbackImage } } + .padding(.top, 10) + .cornerRadius(5) + } + + var fallbackImage: some View { + HStack { + Text(item.unwrappedTitle.prefix(1)) + .font(Font.system(size: 32, weight: .bold)) + .frame(alignment: .bottomLeading) + .foregroundColor(Gradient.randomColor(str: item.unwrappedTitle, offset: 1)) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Gradient.randomColor(str: item.unwrappedTitle, offset: 0)) + .background(LinearGradient(gradient: Gradient(fromStr: item.unwrappedTitle)!, startPoint: .top, endPoint: .bottom)) + .frame(width: 50, height: 50) } var bylineStr: String { diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemLabelView.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemLabelView.swift index aab046607..a9e38ba1d 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemLabelView.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemLabelView.swift @@ -28,7 +28,7 @@ public struct LibraryItemLabelView: View { .cornerRadius(5) .overlay( RoundedRectangle(cornerRadius: 5) - .stroke(Color.isDarkMode ? Color.themeLabelBackground : Color.themeLabelOutline, lineWidth: 1) + .stroke(Color.isDarkMode ? Color.themeLabelBackground : Color.themeLabelBackground, lineWidth: 1) ) } } diff --git a/apple/OmnivoreKit/Sources/Views/FontSizeAdjustmentPopoverView.swift b/apple/OmnivoreKit/Sources/Views/FontSizeAdjustmentPopoverView.swift index c7d436418..cd656e78b 100644 --- a/apple/OmnivoreKit/Sources/Views/FontSizeAdjustmentPopoverView.swift +++ b/apple/OmnivoreKit/Sources/Views/FontSizeAdjustmentPopoverView.swift @@ -90,6 +90,7 @@ public enum WebFont: String, CaseIterable { @AppStorage(UserDefaultKey.preferredWebFont.rawValue) var preferredFont = WebFont.inter.rawValue @AppStorage(UserDefaultKey.prefersHighContrastWebFont.rawValue) var prefersHighContrastText = true @AppStorage(UserDefaultKey.justifyText.rawValue) var justifyText = false + @AppStorage(UserDefaultKey.prefersHideStatusBarInReader.rawValue) var prefersHideStatusBar = false public init( updateReaderPreferences: @escaping () -> Void, @@ -151,6 +152,13 @@ public enum WebFont: String, CaseIterable { updateReaderPreferences() } + Toggle("Hide Status Bar", isOn: $prefersHideStatusBar) + .frame(height: 40) + .padding(.trailing, 6) + .onChange(of: prefersHideStatusBar) { _ in + updateReaderPreferences() + } + Spacer() } .padding(.horizontal, 30) diff --git a/apple/OmnivoreKit/Sources/Views/TextChip.swift b/apple/OmnivoreKit/Sources/Views/TextChip.swift index 6500a014f..6d2f31776 100644 --- a/apple/OmnivoreKit/Sources/Views/TextChip.swift +++ b/apple/OmnivoreKit/Sources/Views/TextChip.swift @@ -92,12 +92,12 @@ public struct TextChip: View { } public struct TextChipButton: View { - public static func makeAddLabelButton(onTap: @escaping () -> Void) -> TextChipButton { - TextChipButton(title: LocalText.labelsGeneric, color: .systemGray6, actionType: .show, negated: false, onTap: onTap) + public static func makeAddLabelButton(color: Color, onTap: @escaping () -> Void) -> TextChipButton { + TextChipButton(title: LocalText.labelsGeneric, color: color, actionType: .show, negated: false, onTap: onTap) } - public static func makeMenuButton(title: String) -> TextChipButton { - TextChipButton(title: title, color: .systemGray6, actionType: .show, negated: false, onTap: {}) + public static func makeMenuButton(title: String, color: Color) -> TextChipButton { + TextChipButton(title: title, color: color, actionType: .show, negated: false, onTap: {}) } public static func makeSearchFilterButton(title: String, onTap: @escaping () -> Void) -> TextChipButton { diff --git a/apple/OmnivoreKit/Sources/Views/UINavigationControllerExtension.swift b/apple/OmnivoreKit/Sources/Views/UINavigationControllerExtension.swift index 2fcc4bddc..32a2a920f 100644 --- a/apple/OmnivoreKit/Sources/Views/UINavigationControllerExtension.swift +++ b/apple/OmnivoreKit/Sources/Views/UINavigationControllerExtension.swift @@ -1,12 +1,12 @@ -#if os(iOS) - - import UIKit - - extension UINavigationController { - // Remove back button text - override open func viewWillLayoutSubviews() { - navigationBar.topItem?.backButtonDisplayMode = .minimal - } - } - -#endif +// #if os(iOS) +// +// import UIKit +// +// extension UINavigationController { +// // Remove back button text +// override open func viewWillLayoutSubviews() { +// navigationBar.topItem?.backButtonDisplayMode = .minimal +// } +// } +// +// #endif