f9f30564fe
The profile view was walking the raw post list and showing pure reactions,
boosts and poll votes as if they were first-class posts — visible as
"Reacted 😊" etc. rows. The timeline already filters these since they are
protocol artefacts rather than authored content; the profile should behave
the same way. The protocol spec is silent on profile filtering (confirmed
by re-reading README.org and the org-social.el reference lib) so this is a
UX choice, but consistency with the timeline is the right default.
- OrgSocialPost.isPureInteraction: single predicate covering
REPLY_TO+MOOD, INCLUDE, and REPLY_TO+POLL_OPTION body-less posts;
migration posts kept because their badge carries the content.
- TimelineFetcher.shouldInclude uses the helper.
- ProfileView's Posts section filters with it.
Separately: RootView now calls RelayClient.registerFeed on launch for the
configured feed URL, keyed by a UserDefaults flag per (relay, feed) pair so
it runs once. A brand-new account otherwise wouldn't appear on the relay
until someone followed it (daily follow-crawl cron) or it hit 404 in
NotificationsViewModel — up to 24h of silence. With self-register the scan
cron picks it up within ~60s, so interactions and notifications start
flowing immediately.
93 lines
3.5 KiB
Swift
93 lines
3.5 KiB
Swift
import SwiftUI
|
|
import OrgSocialKit
|
|
|
|
/// Route identifiers used by `TabView(selection:)`. Kept as an enum so other views
|
|
/// (e.g. TimelineView's empty state) can programmatically switch tabs via a binding.
|
|
enum RootTab: Hashable {
|
|
case timeline, notifications, discover, groups, profile
|
|
}
|
|
|
|
struct RootView: View {
|
|
@AppStorage("publicFeedURL") private var publicFeedURL = ""
|
|
@AppStorage("vfileURL") private var vfileURL = ""
|
|
@AppStorage("githubToken") private var githubToken = ""
|
|
@AppStorage("codebergToken") private var codebergToken = ""
|
|
@AppStorage("webdavURL") private var webdavURL = ""
|
|
|
|
@State private var selectedTab: RootTab = .timeline
|
|
|
|
// User is "configured" once they have both a public feed URL and some kind of credential
|
|
// for publishing. This gate avoids landing on an empty timeline before first-time setup.
|
|
private var isConfigured: Bool {
|
|
guard !publicFeedURL.isEmpty, URL(string: publicFeedURL) != nil else { return false }
|
|
return !vfileURL.isEmpty || !githubToken.isEmpty || !codebergToken.isEmpty || !webdavURL.isEmpty
|
|
}
|
|
|
|
var body: some View {
|
|
if isConfigured {
|
|
tabs
|
|
} else {
|
|
WelcomeView()
|
|
}
|
|
}
|
|
|
|
private var tabs: some View {
|
|
TabView(selection: $selectedTab) {
|
|
TimelineView(selectedTab: $selectedTab)
|
|
.tabItem {
|
|
Label("Timeline", systemImage: "house")
|
|
}
|
|
.tag(RootTab.timeline)
|
|
|
|
NotificationsView()
|
|
.tabItem {
|
|
Label("Notifications", systemImage: "bell")
|
|
}
|
|
.tag(RootTab.notifications)
|
|
|
|
DiscoverView()
|
|
.tabItem {
|
|
Label("Discover", systemImage: "globe")
|
|
}
|
|
.tag(RootTab.discover)
|
|
|
|
GroupsView()
|
|
.tabItem {
|
|
Label("Groups", systemImage: "person.3")
|
|
}
|
|
.tag(RootTab.groups)
|
|
|
|
OwnProfileView()
|
|
.tabItem {
|
|
Label("Profile", systemImage: "person.circle")
|
|
}
|
|
.tag(RootTab.profile)
|
|
}
|
|
.task { await registerWithRelayIfNeeded() }
|
|
}
|
|
|
|
/// Proactively registers the user's feed URL with the relay so it starts
|
|
/// getting crawled (~every minute) instead of waiting for someone to
|
|
/// follow the feed and for the relay's daily follow-crawl to pick it up.
|
|
/// A brand-new account otherwise wouldn't appear anywhere on the relay
|
|
/// for up to 24 hours. The registration is best-effort: a UserDefaults
|
|
/// flag avoids repeating it every launch for the same (feed, relay) pair.
|
|
private func registerWithRelayIfNeeded() async {
|
|
let relayRaw = UserDefaults.standard.string(forKey: "relayURL") ?? ""
|
|
guard !publicFeedURL.isEmpty,
|
|
let feedURL = URL(string: publicFeedURL),
|
|
let relayURL = URL(string: relayRaw) else { return }
|
|
|
|
let registeredKey = "relayRegistration.\(relayURL.absoluteString).\(feedURL.absoluteString)"
|
|
if UserDefaults.standard.bool(forKey: registeredKey) { return }
|
|
|
|
do {
|
|
try await RelayClient().registerFeed(feedURL, with: relayURL)
|
|
UserDefaults.standard.set(true, forKey: registeredKey)
|
|
} catch {
|
|
// Silent: the next launch will retry, and notifications/timeline
|
|
// code already re-registers reactively on 404.
|
|
}
|
|
}
|
|
}
|