-
-
Notifications
You must be signed in to change notification settings - Fork 274
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
first stages of a swiftui list for notifications
changes to keep building that will quickly become moot Starting to implement SwiftUI version of notifications project update forgotten Switch out whole view controller when testing grouped notifications. make old view work again Bump deployment target to iOS 17 better view model. follow button loads correctly, showing followers list or account works. mostly kind of working rename rename
- Loading branch information
1 parent
571d736
commit 7814812
Showing
27 changed files
with
1,386 additions
and
103 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
Mastodon/In Progress New Layout and Datamodel/InlinePostPreview.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// Copyright © 2025 Mastodon gGmbH. All rights reserved. | ||
// | ||
// InlinePostPreview.swift | ||
// Design | ||
// | ||
// Created by Sam on 2024-05-08. | ||
// | ||
|
||
import SwiftUI | ||
import MastodonSDK | ||
|
||
struct InlinePostPreview: View { | ||
let viewModel: Mastodon.Entity.Status.ViewModel | ||
|
||
var body: some View { | ||
VStack(alignment: .leading) { | ||
HStack(spacing: 4) { | ||
if viewModel.needsUserAttribution { | ||
RoundedRectangle(cornerRadius: 4) | ||
.frame(width: 16, height: 16) | ||
Text(viewModel.accountDisplayName ?? "") | ||
.bold() | ||
Text(viewModel.accountFullName ?? "") | ||
.foregroundStyle(.secondary) | ||
Spacer(minLength: 0) | ||
} else if viewModel.isPinned { | ||
// This *should* be a Label but it acts funky when this is in a List (i.e. in UserList) | ||
Group { | ||
Image(systemName: "pin.fill") | ||
Text("Pinned") | ||
} | ||
.bold() | ||
.foregroundStyle(.secondary) | ||
.imageScale(.small) | ||
} | ||
} | ||
.lineLimit(1) | ||
.font(.subheadline) | ||
Text(viewModel.content) | ||
.lineLimit(3) | ||
} | ||
.padding(8) | ||
.frame(maxWidth: .infinity) | ||
.overlay { | ||
RoundedRectangle(cornerRadius: 8) | ||
.fill(.clear) | ||
.stroke(.separator) | ||
} | ||
} | ||
} | ||
|
||
|
||
//#Preview { | ||
// VStack { | ||
// InlinePostPreview(post: SampleData.samplePost) | ||
// InlinePostPreview(post: SampleData.samplePost, needsUserAttribution: false, isPinned: true) | ||
// } | ||
// .padding() | ||
//} |
171 changes: 171 additions & 0 deletions
171
Mastodon/In Progress New Layout and Datamodel/NotificationListViewController.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
// Copyright © 2025 Mastodon gGmbH. All rights reserved. | ||
|
||
import SwiftUI | ||
import MastodonCore | ||
import MastodonSDK | ||
import Combine | ||
|
||
class NotificationListViewController: UIHostingController<NotificationListView> { | ||
|
||
init() { | ||
let viewModel = NotificationListViewModel() | ||
let root = NotificationListView(viewModel: viewModel) | ||
super.init(rootView: root) | ||
viewModel.navigateToScene = { [weak self] scene, transition in | ||
guard let self else { return } | ||
self.sceneCoordinator?.present(scene: scene, from: self, transition: transition) | ||
} | ||
} | ||
|
||
required init?(coder aDecoder: NSCoder) { | ||
fatalError("init(coder:) not implemented for NotificationListViewController") | ||
} | ||
} | ||
|
||
fileprivate enum ListType { | ||
case everything | ||
case mentions | ||
|
||
var pickerLabel: String { | ||
switch self { | ||
case .everything: | ||
"EVERYTHING" | ||
case .mentions: | ||
"MENTIONS" | ||
} | ||
} | ||
|
||
var feedKind: MastodonFeedKind { | ||
switch self { | ||
case .everything: | ||
return .notificationsAll | ||
case .mentions: | ||
return .notificationsMentionsOnly | ||
} | ||
} | ||
} | ||
extension ListType: Identifiable { | ||
var id: String { | ||
return pickerLabel | ||
} | ||
} | ||
|
||
struct NotificationListView: View { | ||
@ObservedObject private var viewModel: NotificationListViewModel | ||
|
||
fileprivate init(viewModel: NotificationListViewModel) { | ||
self.viewModel = viewModel | ||
} | ||
|
||
var body: some View { | ||
VStack { | ||
HStack { | ||
Spacer() | ||
Picker(selection: $viewModel.displayedNotifications) { | ||
ForEach( | ||
[ListType.everything, .mentions] | ||
) { | ||
Text($0.pickerLabel) | ||
.tag($0) | ||
} | ||
} label: { | ||
} | ||
.pickerStyle(.segmented) | ||
Spacer() | ||
} | ||
|
||
List { | ||
ForEach(viewModel.notificationItems) { item in | ||
rowView(item) | ||
.onTapGesture { | ||
didTap(item: item) | ||
} | ||
} | ||
} | ||
.listStyle(.plain) | ||
} | ||
|
||
} | ||
|
||
@ViewBuilder func rowView(_ notificationListItem: NotificationListItem) -> some View { | ||
switch notificationListItem { | ||
case .bottomLoader, .middleLoader: | ||
Text("loader not yet implemented") | ||
case .filteredNotificationsInfo: | ||
Text("filtered notifications not yet implemented") | ||
case .notification(let feedItemIdentifier): | ||
// TODO: implement unread using Mastodon.Entity.Marker | ||
let viewModel = NotificationRowViewModel.viewModel(feedItemIdentifier: feedItemIdentifier, isUnread: false) | ||
GroupedNotificationRowView(viewModel: viewModel) | ||
} | ||
} | ||
|
||
func didTap(item: NotificationListItem) { | ||
switch item { | ||
case .filteredNotificationsInfo: | ||
return | ||
case .notification(let identifier): | ||
if let notificationInfo = | ||
MastodonFeedItemCacheManager.shared.cachedItem(identifier) as? NotificationInfo { | ||
guard let authBox = AuthenticationServiceProvider.shared.currentActiveUser.value, let me = authBox.cachedAccount else { return } | ||
switch (notificationInfo.type, notificationInfo.isGrouped) { | ||
case (.follow, false): | ||
guard let notificationAuthor = notificationInfo.primaryAuthorAccount else { return } | ||
viewModel.navigateToScene?(.profile(.notMe(me: me, displayAccount: notificationAuthor, relationship: MastodonFeedItemCacheManager.shared.currentRelationship(toAccount: notificationAuthor.id))), .show) | ||
case (.follow, true): | ||
viewModel.navigateToScene?(.follower(viewModel: FollowerListViewModel(authenticationBox: authBox, domain: me.domain, userID: me.id)), .show) | ||
default: | ||
break | ||
} | ||
} | ||
default: | ||
return | ||
} | ||
} | ||
} | ||
|
||
@MainActor | ||
fileprivate class NotificationListViewModel: ObservableObject { | ||
|
||
var navigateToScene: ((SceneCoordinator.Scene, SceneCoordinator.Transition)->())? | ||
|
||
@Published var displayedNotifications: ListType = .everything { | ||
didSet { | ||
createNewFeedLoader() | ||
} | ||
} | ||
@Published var notificationItems: [NotificationListItem] = [] | ||
|
||
private var feedSubscription: AnyCancellable? | ||
private var feedLoader = MastodonFeedLoader(kind: .notificationsAll) | ||
|
||
init() { | ||
createNewFeedLoader() | ||
} | ||
|
||
private func createNewFeedLoader() { | ||
feedLoader = MastodonFeedLoader(kind: displayedNotifications.feedKind) | ||
feedSubscription = feedLoader.$records | ||
.receive(on: DispatchQueue.main) | ||
.sink { [weak self] records in | ||
// TODO: add middle loader and bottom loader? | ||
let updatedItems = records.compactMap { | ||
NotificationListItem.fromMastodonFeedItemIdentifier($0) | ||
} | ||
// TODO: add the filtered notifications announcement if needed | ||
self?.notificationItems = updatedItems | ||
} | ||
feedLoader.loadMore(newestAnchor: nil, oldestAnchor: nil) | ||
} | ||
} | ||
|
||
extension NotificationListItem { | ||
static func fromMastodonFeedItemIdentifier(_ feedItem: MastodonFeedItemIdentifier) -> NotificationListItem? { | ||
switch feedItem { | ||
case .notification, .notificationGroup: | ||
return .notification(feedItem) | ||
case .status: | ||
return nil | ||
} | ||
} | ||
} |
Oops, something went wrong.