Each is named in the toolbar's menu rather than hidden behind one button
that says "page": reaching Settings here should be no harder than in a
browser. Admin appears only for an admin, because the server sends that
page to admins alone and a link for anyone else leads to a refusal.
Settings, the directory, Popular and OPML are dialogs the page opens by
name rather than routes of their own, so they are reached by calling them
once it has loaded -- prefsModal(), opmlModal(), selectFeed(':directory').
Asking a moment after the load avoids racing the page wiring them up. The
admin page is a real route and just loads. Asking for the screen already
showing runs the script without reloading, or the page would reload to sit
exactly where it already was.
This is a decision as much as a change, and worth writing down: the goal
was never a native app with no web view in it. These screens are
form-heavy, rarely opened, admin-gated in places, and they work. Rewriting
them would have been the largest part of the job for the smallest return.
The test opens the menu, chooses Settings, and looks for the page's own
dialog, so a menu that opens the page but not the thing asked for fails
rather than passing. It also found that the menu had no accessibility
label, which it should have had anyway.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
56 lines
2.3 KiB
Swift
56 lines
2.3 KiB
Swift
import SwiftUI
|
|
|
|
/// The native interface: feeds beside items, with the player bar under both.
|
|
///
|
|
/// What is not here yet has somewhere to go rather than being missing. The item pane is #4 and
|
|
/// the management screens are #5; until then the toolbar's Page button opens ipx's own page,
|
|
/// which is still the whole app in a web view and can do everything this cannot.
|
|
struct LibraryView: View {
|
|
@ObservedObject var store: LibraryStore
|
|
@ObservedObject var playback: Playback
|
|
/// The file is the one asked for, or the item's first playable one.
|
|
var play: (IPX.Entry, IPX.Enclosure?) -> Void
|
|
var openPage: (WebViewController.Screen) -> Void
|
|
|
|
/// Everything that lives in the page, named rather than hidden behind one button: it should
|
|
/// be as easy to reach Settings here as it is in a browser.
|
|
private var pageMenu: some View {
|
|
Menu {
|
|
Button { openPage(.settings) } label: { Label("Settings", systemImage: "gearshape") }
|
|
Button { openPage(.directory) } label: { Label("Directory", systemImage: "square.grid.2x2") }
|
|
Button { openPage(.popular) } label: { Label("Popular", systemImage: "star") }
|
|
Button { openPage(.opml) } label: { Label("Import or export OPML", systemImage: "square.and.arrow.up") }
|
|
if store.isAdmin {
|
|
Divider()
|
|
Button { openPage(.admin) } label: { Label("Admin", systemImage: "wrench.and.screwdriver") }
|
|
}
|
|
Divider()
|
|
Button { openPage(.page) } label: { Label("Open the full page", systemImage: "safari") }
|
|
} label: {
|
|
Image(systemName: "ellipsis.circle")
|
|
}
|
|
.accessibilityLabel("More")
|
|
.help("Settings, the directory, OPML")
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationSplitView {
|
|
FeedListView(store: store)
|
|
.background(Glass.Wash())
|
|
} detail: {
|
|
ItemListView(store: store, playback: playback, play: play)
|
|
.toolbar {
|
|
ToolbarItem(placement: .primaryAction) { pageMenu }
|
|
}
|
|
}
|
|
.tint(Glass.accent)
|
|
.task {
|
|
await store.refreshFeeds()
|
|
store.reload()
|
|
}
|
|
.safeAreaInset(edge: .bottom, spacing: 0) {
|
|
PlayerBarView(playback: playback)
|
|
}
|
|
}
|
|
}
|