mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-06-14 19:29:31 +00:00
Direct message + empty screen for notifications and messages
This commit is contained in:
parent
88b56fe016
commit
e1ad5efd80
|
@ -17,7 +17,7 @@
|
||||||
9F35DB44294F9A7D00B3281A /* Status in Frameworks */ = {isa = PBXBuildFile; productRef = 9F35DB43294F9A7D00B3281A /* Status */; };
|
9F35DB44294F9A7D00B3281A /* Status in Frameworks */ = {isa = PBXBuildFile; productRef = 9F35DB43294F9A7D00B3281A /* Status */; };
|
||||||
9F35DB4729506F6600B3281A /* NotificationTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F35DB4629506F6600B3281A /* NotificationTab.swift */; };
|
9F35DB4729506F6600B3281A /* NotificationTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F35DB4629506F6600B3281A /* NotificationTab.swift */; };
|
||||||
9F35DB4A29506FA100B3281A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9F35DB4929506FA100B3281A /* Notifications */; };
|
9F35DB4A29506FA100B3281A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9F35DB4929506FA100B3281A /* Notifications */; };
|
||||||
9F35DB4C2952005C00B3281A /* AccountTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F35DB4B2952005C00B3281A /* AccountTab.swift */; };
|
9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F35DB4B2952005C00B3281A /* MessagesTab.swift */; };
|
||||||
9F398AA62935FE8A00A889F2 /* AppRouteur.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F398AA52935FE8A00A889F2 /* AppRouteur.swift */; };
|
9F398AA62935FE8A00A889F2 /* AppRouteur.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F398AA52935FE8A00A889F2 /* AppRouteur.swift */; };
|
||||||
9F398AA92935FFDB00A889F2 /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9F398AA82935FFDB00A889F2 /* Account */; };
|
9F398AA92935FFDB00A889F2 /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9F398AA82935FFDB00A889F2 /* Account */; };
|
||||||
9F398AAB2935FFDB00A889F2 /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = 9F398AAA2935FFDB00A889F2 /* Models */; };
|
9F398AAB2935FFDB00A889F2 /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = 9F398AAA2935FFDB00A889F2 /* Models */; };
|
||||||
|
@ -25,6 +25,7 @@
|
||||||
9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F55C68C2955968700F94077 /* ExploreTab.swift */; };
|
9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F55C68C2955968700F94077 /* ExploreTab.swift */; };
|
||||||
9F55C6902955993C00F94077 /* Explore in Frameworks */ = {isa = PBXBuildFile; productRef = 9F55C68F2955993C00F94077 /* Explore */; };
|
9F55C6902955993C00F94077 /* Explore in Frameworks */ = {isa = PBXBuildFile; productRef = 9F55C68F2955993C00F94077 /* Explore */; };
|
||||||
9F5E581929545BE700A53960 /* Env in Frameworks */ = {isa = PBXBuildFile; productRef = 9F5E581829545BE700A53960 /* Env */; };
|
9F5E581929545BE700A53960 /* Env in Frameworks */ = {isa = PBXBuildFile; productRef = 9F5E581829545BE700A53960 /* Env */; };
|
||||||
|
9F7335EA2966B3F800AFF0BA /* Conversations in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7335E92966B3F800AFF0BA /* Conversations */; };
|
||||||
9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAE4ACA293783B000772766 /* SettingsTab.swift */; };
|
9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAE4ACA293783B000772766 /* SettingsTab.swift */; };
|
||||||
9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAE4ACD29379A5A00772766 /* KeychainSwift */; };
|
9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAE4ACD29379A5A00772766 /* KeychainSwift */; };
|
||||||
9FAE4AD129379AD600772766 /* AppAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAE4AD029379AD600772766 /* AppAccount.swift */; };
|
9FAE4AD129379AD600772766 /* AppAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAE4AD029379AD600772766 /* AppAccount.swift */; };
|
||||||
|
@ -49,7 +50,7 @@
|
||||||
9F35DB45294FA04C00B3281A /* DesignSystem */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = DesignSystem; path = Packages/DesignSystem; sourceTree = "<group>"; };
|
9F35DB45294FA04C00B3281A /* DesignSystem */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = DesignSystem; path = Packages/DesignSystem; sourceTree = "<group>"; };
|
||||||
9F35DB4629506F6600B3281A /* NotificationTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationTab.swift; sourceTree = "<group>"; };
|
9F35DB4629506F6600B3281A /* NotificationTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationTab.swift; sourceTree = "<group>"; };
|
||||||
9F35DB4829506F7F00B3281A /* Notifications */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Notifications; path = Packages/Notifications; sourceTree = "<group>"; };
|
9F35DB4829506F7F00B3281A /* Notifications */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Notifications; path = Packages/Notifications; sourceTree = "<group>"; };
|
||||||
9F35DB4B2952005C00B3281A /* AccountTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTab.swift; sourceTree = "<group>"; };
|
9F35DB4B2952005C00B3281A /* MessagesTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesTab.swift; sourceTree = "<group>"; };
|
||||||
9F398AA32935F90100A889F2 /* Models */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Models; path = Packages/Models; sourceTree = "<group>"; };
|
9F398AA32935F90100A889F2 /* Models */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Models; path = Packages/Models; sourceTree = "<group>"; };
|
||||||
9F398AA52935FE8A00A889F2 /* AppRouteur.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteur.swift; sourceTree = "<group>"; };
|
9F398AA52935FE8A00A889F2 /* AppRouteur.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteur.swift; sourceTree = "<group>"; };
|
||||||
9F398AAC2936005300A889F2 /* Account */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Account; path = Packages/Account; sourceTree = "<group>"; };
|
9F398AAC2936005300A889F2 /* Account */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Account; path = Packages/Account; sourceTree = "<group>"; };
|
||||||
|
@ -57,6 +58,7 @@
|
||||||
9F55C68C2955968700F94077 /* ExploreTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreTab.swift; sourceTree = "<group>"; };
|
9F55C68C2955968700F94077 /* ExploreTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreTab.swift; sourceTree = "<group>"; };
|
||||||
9F55C68E295598F900F94077 /* Explore */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Explore; path = Packages/Explore; sourceTree = "<group>"; };
|
9F55C68E295598F900F94077 /* Explore */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Explore; path = Packages/Explore; sourceTree = "<group>"; };
|
||||||
9F5E581729545B5500A53960 /* Env */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Env; path = Packages/Env; sourceTree = "<group>"; };
|
9F5E581729545B5500A53960 /* Env */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Env; path = Packages/Env; sourceTree = "<group>"; };
|
||||||
|
9F7335E82966B3DC00AFF0BA /* Conversations */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Conversations; path = Packages/Conversations; sourceTree = "<group>"; };
|
||||||
9FAE4AC8293774FF00772766 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
9FAE4AC8293774FF00772766 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||||
9FAE4ACA293783B000772766 /* SettingsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = "<group>"; };
|
9FAE4ACA293783B000772766 /* SettingsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = "<group>"; };
|
||||||
9FAE4AD029379AD600772766 /* AppAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAccount.swift; sourceTree = "<group>"; };
|
9FAE4AD029379AD600772766 /* AppAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAccount.swift; sourceTree = "<group>"; };
|
||||||
|
@ -76,6 +78,7 @@
|
||||||
files = (
|
files = (
|
||||||
9F55C6902955993C00F94077 /* Explore in Frameworks */,
|
9F55C6902955993C00F94077 /* Explore in Frameworks */,
|
||||||
9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */,
|
9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */,
|
||||||
|
9F7335EA2966B3F800AFF0BA /* Conversations in Frameworks */,
|
||||||
9F398AA92935FFDB00A889F2 /* Account in Frameworks */,
|
9F398AA92935FFDB00A889F2 /* Account in Frameworks */,
|
||||||
9FBFE64E292A72BD00C250E9 /* Network in Frameworks */,
|
9FBFE64E292A72BD00C250E9 /* Network in Frameworks */,
|
||||||
9FD542E72962D2FF0045321A /* Lists in Frameworks */,
|
9FD542E72962D2FF0045321A /* Lists in Frameworks */,
|
||||||
|
@ -115,7 +118,7 @@
|
||||||
9FE151A4293C90EA00E9683D /* Settings */,
|
9FE151A4293C90EA00E9683D /* Settings */,
|
||||||
9F398AB229360A4C00A889F2 /* TimelineTab.swift */,
|
9F398AB229360A4C00A889F2 /* TimelineTab.swift */,
|
||||||
9F35DB4629506F6600B3281A /* NotificationTab.swift */,
|
9F35DB4629506F6600B3281A /* NotificationTab.swift */,
|
||||||
9F35DB4B2952005C00B3281A /* AccountTab.swift */,
|
9F35DB4B2952005C00B3281A /* MessagesTab.swift */,
|
||||||
9F55C68C2955968700F94077 /* ExploreTab.swift */,
|
9F55C68C2955968700F94077 /* ExploreTab.swift */,
|
||||||
9F2B92F5295AE04800DE16D0 /* Tabs.swift */,
|
9F2B92F5295AE04800DE16D0 /* Tabs.swift */,
|
||||||
);
|
);
|
||||||
|
@ -140,6 +143,7 @@
|
||||||
9FBFE63A292A715500C250E9 /* Products */,
|
9FBFE63A292A715500C250E9 /* Products */,
|
||||||
9FBFE64C292A72BD00C250E9 /* Frameworks */,
|
9FBFE64C292A72BD00C250E9 /* Frameworks */,
|
||||||
9F398AAC2936005300A889F2 /* Account */,
|
9F398AAC2936005300A889F2 /* Account */,
|
||||||
|
9F7335E82966B3DC00AFF0BA /* Conversations */,
|
||||||
9F35DB45294FA04C00B3281A /* DesignSystem */,
|
9F35DB45294FA04C00B3281A /* DesignSystem */,
|
||||||
9F55C68E295598F900F94077 /* Explore */,
|
9F55C68E295598F900F94077 /* Explore */,
|
||||||
9F5E581729545B5500A53960 /* Env */,
|
9F5E581729545B5500A53960 /* Env */,
|
||||||
|
@ -217,6 +221,7 @@
|
||||||
9F5E581829545BE700A53960 /* Env */,
|
9F5E581829545BE700A53960 /* Env */,
|
||||||
9F55C68F2955993C00F94077 /* Explore */,
|
9F55C68F2955993C00F94077 /* Explore */,
|
||||||
9FD542E62962D2FF0045321A /* Lists */,
|
9FD542E62962D2FF0045321A /* Lists */,
|
||||||
|
9F7335E92966B3F800AFF0BA /* Conversations */,
|
||||||
);
|
);
|
||||||
productName = IceCubesApp;
|
productName = IceCubesApp;
|
||||||
productReference = 9FBFE639292A715500C250E9 /* IceCubesApp.app */;
|
productReference = 9FBFE639292A715500C250E9 /* IceCubesApp.app */;
|
||||||
|
@ -277,7 +282,7 @@
|
||||||
files = (
|
files = (
|
||||||
9FE151A6293C90F900E9683D /* IconSelectorView.swift in Sources */,
|
9FE151A6293C90F900E9683D /* IconSelectorView.swift in Sources */,
|
||||||
9F2B92FC295DA94500DE16D0 /* InstanceInfoView.swift in Sources */,
|
9F2B92FC295DA94500DE16D0 /* InstanceInfoView.swift in Sources */,
|
||||||
9F35DB4C2952005C00B3281A /* AccountTab.swift in Sources */,
|
9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */,
|
||||||
9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */,
|
9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */,
|
||||||
9FAE4AD32937A0C600772766 /* AppAccountsManager.swift in Sources */,
|
9FAE4AD32937A0C600772766 /* AppAccountsManager.swift in Sources */,
|
||||||
9F2B92FF295EB87100DE16D0 /* AppAccountView.swift in Sources */,
|
9F2B92FF295EB87100DE16D0 /* AppAccountView.swift in Sources */,
|
||||||
|
@ -566,6 +571,10 @@
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = Env;
|
productName = Env;
|
||||||
};
|
};
|
||||||
|
9F7335E92966B3F800AFF0BA /* Conversations */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = Conversations;
|
||||||
|
};
|
||||||
9FAE4ACD29379A5A00772766 /* KeychainSwift */ = {
|
9FAE4ACD29379A5A00772766 /* KeychainSwift */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 9FAE4ACC29379A5A00772766 /* XCRemoteSwiftPackageReference "keychain-swift" */;
|
package = 9FAE4ACC29379A5A00772766 /* XCRemoteSwiftPackageReference "keychain-swift" */;
|
||||||
|
|
|
@ -37,8 +37,8 @@ extension View {
|
||||||
switch destination {
|
switch destination {
|
||||||
case let .replyToStatusEditor(status):
|
case let .replyToStatusEditor(status):
|
||||||
StatusEditorView(mode: .replyTo(status: status))
|
StatusEditorView(mode: .replyTo(status: status))
|
||||||
case .newStatusEditor:
|
case let .newStatusEditor(visibility):
|
||||||
StatusEditorView(mode: .new)
|
StatusEditorView(mode: .new(vivibilty: visibility))
|
||||||
case let .editStatusEditor(status):
|
case let .editStatusEditor(status):
|
||||||
StatusEditorView(mode: .edit(status: status))
|
StatusEditorView(mode: .edit(status: status))
|
||||||
case let .quoteStatusEditor(status):
|
case let .quoteStatusEditor(status):
|
||||||
|
|
|
@ -49,7 +49,7 @@ struct IceCubesApp: App {
|
||||||
.onChange(of: appAccountsManager.currentClient) { newClient in
|
.onChange(of: appAccountsManager.currentClient) { newClient in
|
||||||
setNewClientsInEnv(client: newClient)
|
setNewClientsInEnv(client: newClient)
|
||||||
if newClient.isAuth {
|
if newClient.isAuth {
|
||||||
watcher.watch(stream: .user)
|
watcher.watch(streams: [.user, .direct])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: theme.primaryBackgroundColor) { newValue in
|
.onChange(of: theme.primaryBackgroundColor) { newValue in
|
||||||
|
@ -66,6 +66,15 @@ struct IceCubesApp: App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func badgeFor(tab: Tab) -> Int {
|
||||||
|
if tab == .notifications && selectedTab != tab {
|
||||||
|
return watcher.unreadNotificationsCount
|
||||||
|
} else if tab == .messages && selectedTab != tab {
|
||||||
|
return watcher.unreadMessagesCount
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
private var tabBarView: some View {
|
private var tabBarView: some View {
|
||||||
TabView(selection: .init(get: {
|
TabView(selection: .init(get: {
|
||||||
selectedTab
|
selectedTab
|
||||||
|
@ -85,7 +94,7 @@ struct IceCubesApp: App {
|
||||||
tab.label
|
tab.label
|
||||||
}
|
}
|
||||||
.tag(tab)
|
.tag(tab)
|
||||||
.badge(tab == .notifications ? watcher.unreadNotificationsCount : 0)
|
.badge(badgeFor(tab: tab))
|
||||||
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .tabBar)
|
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .tabBar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +125,7 @@ struct IceCubesApp: App {
|
||||||
case .background:
|
case .background:
|
||||||
watcher.stopWatching()
|
watcher.stopWatching()
|
||||||
case .active:
|
case .active:
|
||||||
watcher.watch(stream: .user)
|
watcher.watch(streams: [.user, .direct])
|
||||||
case .inactive:
|
case .inactive:
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -18,7 +18,7 @@ struct ExploreTab: View {
|
||||||
.withAppRouteur()
|
.withAppRouteur()
|
||||||
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
|
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
statusEditorToolbarItem(routeurPath: routeurPath)
|
statusEditorToolbarItem(routeurPath: routeurPath, visibility: .pub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.environmentObject(routeurPath)
|
.environmentObject(routeurPath)
|
||||||
|
|
|
@ -4,8 +4,11 @@ import Network
|
||||||
import Account
|
import Account
|
||||||
import Models
|
import Models
|
||||||
import Shimmer
|
import Shimmer
|
||||||
|
import Conversations
|
||||||
|
import Env
|
||||||
|
|
||||||
struct AccountTab: View {
|
struct MessagesTab: View {
|
||||||
|
@EnvironmentObject private var watcher: StreamWatcher
|
||||||
@EnvironmentObject private var client: Client
|
@EnvironmentObject private var client: Client
|
||||||
@EnvironmentObject private var currentAccount: CurrentAccount
|
@EnvironmentObject private var currentAccount: CurrentAccount
|
||||||
@StateObject private var routeurPath = RouterPath()
|
@StateObject private var routeurPath = RouterPath()
|
||||||
|
@ -13,23 +16,14 @@ struct AccountTab: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack(path: $routeurPath.path) {
|
NavigationStack(path: $routeurPath.path) {
|
||||||
if let account = currentAccount.account {
|
ConversationsListView()
|
||||||
AccountDetailView(account: account)
|
.withAppRouteur()
|
||||||
.withAppRouteur()
|
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
|
||||||
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
|
.id(currentAccount.account?.id)
|
||||||
.toolbar {
|
|
||||||
statusEditorToolbarItem(routeurPath: routeurPath)
|
|
||||||
}
|
|
||||||
.id(account.id)
|
|
||||||
} else {
|
|
||||||
AccountDetailView(account: .placeholder())
|
|
||||||
.redacted(reason: .placeholder)
|
|
||||||
.shimmering()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.environmentObject(routeurPath)
|
.environmentObject(routeurPath)
|
||||||
.onChange(of: $popToRootTab.wrappedValue) { popToRootTab in
|
.onChange(of: $popToRootTab.wrappedValue) { popToRootTab in
|
||||||
if popToRootTab == .account {
|
if popToRootTab == .messages {
|
||||||
routeurPath.path = []
|
routeurPath.path = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +32,7 @@ struct AccountTab: View {
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
routeurPath.client = client
|
routeurPath.client = client
|
||||||
|
watcher.unreadMessagesCount = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,7 +17,7 @@ struct NotificationsTab: View {
|
||||||
.withAppRouteur()
|
.withAppRouteur()
|
||||||
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
|
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
statusEditorToolbarItem(routeurPath: routeurPath)
|
statusEditorToolbarItem(routeurPath: routeurPath, visibility: .pub)
|
||||||
}
|
}
|
||||||
.id(currentAccount.account?.id)
|
.id(currentAccount.account?.id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Explore
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum Tab: Int, Identifiable, Hashable {
|
enum Tab: Int, Identifiable, Hashable {
|
||||||
case timeline, notifications, explore, account, settings, other
|
case timeline, notifications, explore, messages, settings, other
|
||||||
|
|
||||||
var id: Int {
|
var id: Int {
|
||||||
rawValue
|
rawValue
|
||||||
|
@ -16,7 +16,7 @@ enum Tab: Int, Identifiable, Hashable {
|
||||||
}
|
}
|
||||||
|
|
||||||
static func loggedInTabs() -> [Tab] {
|
static func loggedInTabs() -> [Tab] {
|
||||||
[.timeline, .notifications, .explore, .account, .settings]
|
[.timeline, .notifications, .explore, .messages, .settings]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
@ -28,8 +28,8 @@ enum Tab: Int, Identifiable, Hashable {
|
||||||
NotificationsTab(popToRootTab: popToRootTab)
|
NotificationsTab(popToRootTab: popToRootTab)
|
||||||
case .explore:
|
case .explore:
|
||||||
ExploreTab(popToRootTab: popToRootTab)
|
ExploreTab(popToRootTab: popToRootTab)
|
||||||
case .account:
|
case .messages:
|
||||||
AccountTab(popToRootTab: popToRootTab)
|
MessagesTab(popToRootTab: popToRootTab)
|
||||||
case .settings:
|
case .settings:
|
||||||
SettingsTabs()
|
SettingsTabs()
|
||||||
case .other:
|
case .other:
|
||||||
|
@ -46,8 +46,8 @@ enum Tab: Int, Identifiable, Hashable {
|
||||||
Label("Notifications", systemImage: "bell")
|
Label("Notifications", systemImage: "bell")
|
||||||
case .explore:
|
case .explore:
|
||||||
Label("Explore", systemImage: "magnifyingglass")
|
Label("Explore", systemImage: "magnifyingglass")
|
||||||
case .account:
|
case .messages:
|
||||||
Label("Profile", systemImage: "person.circle")
|
Label("Messages", systemImage: "tray")
|
||||||
case .settings:
|
case .settings:
|
||||||
Label("Settings", systemImage: "gear")
|
Label("Settings", systemImage: "gear")
|
||||||
case .other:
|
case .other:
|
||||||
|
|
|
@ -32,7 +32,7 @@ struct TimelineTab: View {
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
accountButton
|
accountButton
|
||||||
}
|
}
|
||||||
statusEditorToolbarItem(routeurPath: routeurPath)
|
statusEditorToolbarItem(routeurPath: routeurPath, visibility: .pub)
|
||||||
} else {
|
} else {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
addAccountButton
|
addAccountButton
|
||||||
|
|
9
Packages/Conversations/.gitignore
vendored
Normal file
9
Packages/Conversations/.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
.DS_Store
|
||||||
|
/.build
|
||||||
|
/Packages
|
||||||
|
/*.xcodeproj
|
||||||
|
xcuserdata/
|
||||||
|
DerivedData/
|
||||||
|
.swiftpm/config/registries.json
|
||||||
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
|
.netrc
|
33
Packages/Conversations/Package.swift
Normal file
33
Packages/Conversations/Package.swift
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// swift-tools-version: 5.7
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "Conversations",
|
||||||
|
platforms: [
|
||||||
|
.iOS(.v16),
|
||||||
|
],
|
||||||
|
products: [
|
||||||
|
.library(
|
||||||
|
name: "Conversations",
|
||||||
|
targets: ["Conversations"]),
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
.package(name: "Models", path: "../Models"),
|
||||||
|
.package(name: "Network", path: "../Network"),
|
||||||
|
.package(name: "Env", path: "../Env"),
|
||||||
|
.package(name: "DesignSystem", path: "../DesignSystem"),
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
.target(
|
||||||
|
name: "Conversations",
|
||||||
|
dependencies: [
|
||||||
|
.product(name: "Models", package: "Models"),
|
||||||
|
.product(name: "Network", package: "Network"),
|
||||||
|
.product(name: "Env", package: "Env"),
|
||||||
|
.product(name: "DesignSystem", package: "DesignSystem"),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
3
Packages/Conversations/README.md
Normal file
3
Packages/Conversations/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Conversations
|
||||||
|
|
||||||
|
A description of this package.
|
|
@ -0,0 +1,95 @@
|
||||||
|
import SwiftUI
|
||||||
|
import Models
|
||||||
|
import Account
|
||||||
|
import DesignSystem
|
||||||
|
import Env
|
||||||
|
import Network
|
||||||
|
|
||||||
|
struct ConversationsListRow: View {
|
||||||
|
@EnvironmentObject private var client: Client
|
||||||
|
@EnvironmentObject private var routerPath: RouterPath
|
||||||
|
@EnvironmentObject private var theme: Theme
|
||||||
|
|
||||||
|
let conversation: Conversation
|
||||||
|
@ObservedObject var viewModel: ConversationsListViewModel
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
HStack(alignment: .top, spacing: 8) {
|
||||||
|
AvatarView(url: conversation.accounts.first!.avatar)
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
HStack {
|
||||||
|
Text(conversation.accounts.map{ $0.safeDisplayName }.joined(separator: ", "))
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(theme.labelColor)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
Spacer()
|
||||||
|
if conversation.unread {
|
||||||
|
Circle()
|
||||||
|
.foregroundColor(theme.tintColor)
|
||||||
|
.frame(width: 10, height: 10)
|
||||||
|
}
|
||||||
|
Text(conversation.lastStatus.createdAt.formatted)
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
Text(conversation.lastStatus.content.asRawText)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture {
|
||||||
|
Task {
|
||||||
|
await viewModel.markAsRead(conversation: conversation)
|
||||||
|
}
|
||||||
|
routerPath.navigate(to: .statusDetail(id: conversation.lastStatus.id))
|
||||||
|
}
|
||||||
|
.padding(.top, 4)
|
||||||
|
actionsView
|
||||||
|
.padding(.bottom, 4)
|
||||||
|
}
|
||||||
|
.contextMenu {
|
||||||
|
contextMenu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var actionsView: some View {
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
Button {
|
||||||
|
routerPath.presentedSheet = .replyToStatusEditor(status: conversation.lastStatus)
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "arrowshape.turn.up.left.fill")
|
||||||
|
}
|
||||||
|
Menu {
|
||||||
|
contextMenu
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "ellipsis")
|
||||||
|
.frame(width: 30, height: 30)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.leading, 48)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var contextMenu: some View {
|
||||||
|
Button {
|
||||||
|
Task {
|
||||||
|
await viewModel.markAsRead(conversation: conversation)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label("Mark as read", systemImage: "eye")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(role: .destructive) {
|
||||||
|
Task {
|
||||||
|
await viewModel.delete(conversation: conversation)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label("Delete", systemImage: "trash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
import SwiftUI
|
||||||
|
import Network
|
||||||
|
import Models
|
||||||
|
import DesignSystem
|
||||||
|
import Shimmer
|
||||||
|
import Env
|
||||||
|
|
||||||
|
public struct ConversationsListView: View {
|
||||||
|
@EnvironmentObject private var routeurPath: RouterPath
|
||||||
|
@EnvironmentObject private var watcher: StreamWatcher
|
||||||
|
@EnvironmentObject private var client: Client
|
||||||
|
@EnvironmentObject private var theme: Theme
|
||||||
|
|
||||||
|
@StateObject private var viewModel = ConversationsListViewModel()
|
||||||
|
|
||||||
|
public init() { }
|
||||||
|
|
||||||
|
private var conversations: [Conversation] {
|
||||||
|
if viewModel.isLoadingFirstPage {
|
||||||
|
return Conversation.placeholders()
|
||||||
|
}
|
||||||
|
return viewModel.conversations
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
ScrollView {
|
||||||
|
LazyVStack {
|
||||||
|
if !conversations.isEmpty || viewModel.isLoadingFirstPage {
|
||||||
|
ForEach(conversations) { conversation in
|
||||||
|
if viewModel.isLoadingFirstPage {
|
||||||
|
ConversationsListRow(conversation: conversation, viewModel: viewModel)
|
||||||
|
.padding(.horizontal, .layoutPadding)
|
||||||
|
.redacted(reason: .placeholder)
|
||||||
|
.shimmering()
|
||||||
|
} else {
|
||||||
|
ConversationsListRow(conversation: conversation, viewModel: viewModel)
|
||||||
|
.padding(.horizontal, .layoutPadding)
|
||||||
|
}
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
} else if conversations.isEmpty && !viewModel.isLoadingFirstPage {
|
||||||
|
EmptyView(iconName: "tray",
|
||||||
|
title: "Inbox Zero",
|
||||||
|
message: "Looking for some social media love? You'll find all your direct messages and private mentions right here. Happy messaging! 📱❤️")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, .layoutPadding)
|
||||||
|
}
|
||||||
|
.scrollContentBackground(.hidden)
|
||||||
|
.background(theme.primaryBackgroundColor)
|
||||||
|
.navigationTitle("Direct Messages")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar {
|
||||||
|
statusEditorToolbarItem(routeurPath: routeurPath, visibility: .direct)
|
||||||
|
}
|
||||||
|
.onChange(of: watcher.latestEvent?.id) { id in
|
||||||
|
if let latestEvent = watcher.latestEvent {
|
||||||
|
viewModel.handleEvent(event: latestEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.refreshable {
|
||||||
|
await viewModel.fetchConversations()
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
viewModel.client = client
|
||||||
|
if client.isAuth {
|
||||||
|
Task {
|
||||||
|
await viewModel.fetchConversations()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class ConversationsListViewModel: ObservableObject {
|
||||||
|
var client: Client?
|
||||||
|
|
||||||
|
@Published var isLoadingFirstPage: Bool = true
|
||||||
|
@Published var conversations: [Conversation] = []
|
||||||
|
|
||||||
|
private let feedbackGenerator = UINotificationFeedbackGenerator()
|
||||||
|
|
||||||
|
public init() { }
|
||||||
|
|
||||||
|
func fetchConversations() async {
|
||||||
|
guard let client else { return }
|
||||||
|
if conversations.isEmpty {
|
||||||
|
isLoadingFirstPage = true
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
conversations = try await client.get(endpoint: Conversations.conversations)
|
||||||
|
isLoadingFirstPage = false
|
||||||
|
} catch {
|
||||||
|
isLoadingFirstPage = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func markAsRead(conversation: Conversation) async {
|
||||||
|
guard let client else { return }
|
||||||
|
_ = try? await client.post(endpoint: Conversations.read(id: conversation.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func delete(conversation: Conversation) async {
|
||||||
|
guard let client else { return }
|
||||||
|
_ = try? await client.delete(endpoint: Conversations.delete(id: conversation.id))
|
||||||
|
await fetchConversations()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleEvent(event: any StreamEvent) {
|
||||||
|
if let event = event as? StreamEventConversation {
|
||||||
|
if let index = conversations.firstIndex(where: { $0.id == event.conversation.id }) {
|
||||||
|
conversations.remove(at: index)
|
||||||
|
}
|
||||||
|
conversations.insert(event.conversation, at: 0)
|
||||||
|
conversations = conversations.sorted(by: { $0.lastStatus.createdAt.asDate > $1.lastStatus.createdAt.asDate })
|
||||||
|
feedbackGenerator.notificationOccurred(.success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct EmptyView: View {
|
||||||
|
public let iconName: String
|
||||||
|
public let title: String
|
||||||
|
public let message: String
|
||||||
|
|
||||||
|
public init(iconName: String, title: String, message: String) {
|
||||||
|
self.iconName = iconName
|
||||||
|
self.title = title
|
||||||
|
self.message = message
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
VStack {
|
||||||
|
Image(systemName: iconName)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(maxHeight: 50)
|
||||||
|
Text(title)
|
||||||
|
.font(.title)
|
||||||
|
.padding(.top, 16)
|
||||||
|
Text(message)
|
||||||
|
.font(.subheadline)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
.padding(.top, 100)
|
||||||
|
.padding(.layoutPadding)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,13 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Env
|
import Env
|
||||||
|
import Models
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
extension View {
|
extension View {
|
||||||
public func statusEditorToolbarItem(routeurPath: RouterPath) -> some ToolbarContent {
|
public func statusEditorToolbarItem(routeurPath: RouterPath, visibility: Models.Visibility) -> some ToolbarContent {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
Button {
|
Button {
|
||||||
routeurPath.presentedSheet = .newStatusEditor
|
routeurPath.presentedSheet = .newStatusEditor(visibility: visibility)
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "square.and.pencil")
|
Image(systemName: "square.and.pencil")
|
||||||
}
|
}
|
||||||
|
@ -16,13 +17,16 @@ extension View {
|
||||||
|
|
||||||
public struct StatusEditorToolbarItem: ToolbarContent {
|
public struct StatusEditorToolbarItem: ToolbarContent {
|
||||||
@EnvironmentObject private var routerPath: RouterPath
|
@EnvironmentObject private var routerPath: RouterPath
|
||||||
|
let visibility: Models.Visibility
|
||||||
|
|
||||||
public init() { }
|
public init(visibility: Models.Visibility) {
|
||||||
|
self.visibility = visibility
|
||||||
|
}
|
||||||
|
|
||||||
public var body: some ToolbarContent {
|
public var body: some ToolbarContent {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
Button {
|
Button {
|
||||||
routerPath.presentedSheet = .newStatusEditor
|
routerPath.presentedSheet = .newStatusEditor(visibility: visibility)
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "square.and.pencil")
|
Image(systemName: "square.and.pencil")
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ public enum RouteurDestinations: Hashable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SheetDestinations: Identifiable {
|
public enum SheetDestinations: Identifiable {
|
||||||
case newStatusEditor
|
case newStatusEditor(visibility: Models.Visibility)
|
||||||
case editStatusEditor(status: Status)
|
case editStatusEditor(status: Status)
|
||||||
case replyToStatusEditor(status: Status)
|
case replyToStatusEditor(status: Status)
|
||||||
case quoteStatusEditor(status: Status)
|
case quoteStatusEditor(status: Status)
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Network
|
||||||
public class StreamWatcher: ObservableObject {
|
public class StreamWatcher: ObservableObject {
|
||||||
private var client: Client?
|
private var client: Client?
|
||||||
private var task: URLSessionWebSocketTask?
|
private var task: URLSessionWebSocketTask?
|
||||||
private var watchedStream: Stream?
|
private var watchedStreams: [Stream] = []
|
||||||
|
|
||||||
private let decoder = JSONDecoder()
|
private let decoder = JSONDecoder()
|
||||||
private let encoder = JSONEncoder()
|
private let encoder = JSONEncoder()
|
||||||
|
@ -14,10 +14,12 @@ public class StreamWatcher: ObservableObject {
|
||||||
public enum Stream: String {
|
public enum Stream: String {
|
||||||
case publicTimeline = "public"
|
case publicTimeline = "public"
|
||||||
case user
|
case user
|
||||||
|
case direct
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published public var events: [any StreamEvent] = []
|
@Published public var events: [any StreamEvent] = []
|
||||||
@Published public var unreadNotificationsCount: Int = 0
|
@Published public var unreadNotificationsCount: Int = 0
|
||||||
|
@Published public var unreadMessagesCount: Int = 0
|
||||||
@Published public var latestEvent: (any StreamEvent)?
|
@Published public var latestEvent: (any StreamEvent)?
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
|
@ -38,15 +40,17 @@ public class StreamWatcher: ObservableObject {
|
||||||
receiveMessage()
|
receiveMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func watch(stream: Stream) {
|
public func watch(streams: [Stream]) {
|
||||||
if client?.isAuth == false && stream == .user {
|
if client?.isAuth == false {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if task == nil {
|
if task == nil {
|
||||||
connect()
|
connect()
|
||||||
}
|
}
|
||||||
watchedStream = stream
|
watchedStreams = streams
|
||||||
sendMessage(message: StreamMessage(type: "subscribe", stream: stream.rawValue))
|
streams.forEach { stream in
|
||||||
|
sendMessage(message: StreamMessage(type: "subscribe", stream: stream.rawValue))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func stopWatching() {
|
public func stopWatching() {
|
||||||
|
@ -75,8 +79,10 @@ public class StreamWatcher: ObservableObject {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
self.events.append(event)
|
self.events.append(event)
|
||||||
self.latestEvent = event
|
self.latestEvent = event
|
||||||
if event is StreamEventNotification {
|
if let event = event as? StreamEventNotification, event.notification.status?.visibility != .direct {
|
||||||
self.unreadNotificationsCount += 1
|
self.unreadNotificationsCount += 1
|
||||||
|
} else if event is StreamEventConversation {
|
||||||
|
self.unreadMessagesCount += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,9 +101,7 @@ public class StreamWatcher: ObservableObject {
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.stopWatching()
|
self.stopWatching()
|
||||||
self.connect()
|
self.connect()
|
||||||
if let watchedStream = self.watchedStream {
|
self.watch(streams: self.watchedStreams)
|
||||||
self.watch(stream: watchedStream)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -120,6 +124,9 @@ public class StreamWatcher: ObservableObject {
|
||||||
case "notification":
|
case "notification":
|
||||||
let notification = try decoder.decode(Notification.self, from: payloadData)
|
let notification = try decoder.decode(Notification.self, from: payloadData)
|
||||||
return StreamEventNotification(notification: notification)
|
return StreamEventNotification(notification: notification)
|
||||||
|
case "conversation":
|
||||||
|
let conversation = try decoder.decode(Conversation.self, from: payloadData)
|
||||||
|
return StreamEventConversation(conversation: conversation)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
17
Packages/Models/Sources/Models/Conversation.swift
Normal file
17
Packages/Models/Sources/Models/Conversation.swift
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct Conversation: Identifiable, Decodable {
|
||||||
|
public let id: String
|
||||||
|
public let unread: Bool
|
||||||
|
public let lastStatus: Status
|
||||||
|
public let accounts: [Account]
|
||||||
|
|
||||||
|
public static func placeholder() -> Conversation {
|
||||||
|
.init(id: UUID().uuidString, unread: false, lastStatus: .placeholder(), accounts: [.placeholder()])
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func placeholders() -> [Conversation] {
|
||||||
|
[.placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder(),
|
||||||
|
.placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder()]
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,3 +46,12 @@ public struct StreamEventNotification: StreamEvent {
|
||||||
self.notification = notification
|
self.notification = notification
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct StreamEventConversation: StreamEvent {
|
||||||
|
public let date = Date()
|
||||||
|
public var id: String { conversation.id }
|
||||||
|
public let conversation: Conversation
|
||||||
|
public init(conversation: Conversation) {
|
||||||
|
self.conversation = conversation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum Conversations: Endpoint {
|
||||||
|
case conversations
|
||||||
|
case delete(id: String)
|
||||||
|
case read(id: String)
|
||||||
|
|
||||||
|
public func path() -> String {
|
||||||
|
switch self {
|
||||||
|
case .conversations:
|
||||||
|
return "conversations"
|
||||||
|
case let .delete(id):
|
||||||
|
return "conversations/\(id)"
|
||||||
|
case let .read(id):
|
||||||
|
return "conversations/\(id)/read"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func queryItems() -> [URLQueryItem]? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,10 +71,16 @@ public struct NotificationsListView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
case let .display(notifications, nextPageState):
|
case let .display(notifications, nextPageState):
|
||||||
ForEach(notifications) { notification in
|
if notifications.isEmpty {
|
||||||
NotificationRowView(notification: notification)
|
EmptyView(iconName: "bell.slash",
|
||||||
Divider()
|
title: "No notifications",
|
||||||
.padding(.vertical, .dividerPadding)
|
message: "Notifications? What notifications? Your notification inbox is looking so empty. Keep on being awesome! 📱😎")
|
||||||
|
} else {
|
||||||
|
ForEach(notifications) { notification in
|
||||||
|
NotificationRowView(notification: notification)
|
||||||
|
Divider()
|
||||||
|
.padding(.vertical, .dividerPadding)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch nextPageState {
|
switch nextPageState {
|
||||||
|
|
|
@ -107,8 +107,13 @@ public class StatusEditorViewModel: ObservableObject {
|
||||||
|
|
||||||
func prepareStatusText() {
|
func prepareStatusText() {
|
||||||
switch mode {
|
switch mode {
|
||||||
|
case let .new(visibility):
|
||||||
|
self.visibility = visibility
|
||||||
case let .replyTo(status):
|
case let .replyTo(status):
|
||||||
var mentionString = "@\(status.reblog?.account.acct ?? status.account.acct)"
|
var mentionString = ""
|
||||||
|
if (status.reblog?.account.acct ?? status.account.acct) != currentAccount?.acct {
|
||||||
|
mentionString = "@\(status.reblog?.account.acct ?? status.account.acct)"
|
||||||
|
}
|
||||||
for mention in status.mentions where mention.acct != currentAccount?.acct {
|
for mention in status.mentions where mention.acct != currentAccount?.acct {
|
||||||
mentionString += " @\(mention.acct)"
|
mentionString += " @\(mention.acct)"
|
||||||
}
|
}
|
||||||
|
@ -193,7 +198,7 @@ public class StatusEditorViewModel: ObservableObject {
|
||||||
if let url = embededStatusURL,
|
if let url = embededStatusURL,
|
||||||
!statusText.string.contains(url.absoluteString) {
|
!statusText.string.contains(url.absoluteString) {
|
||||||
self.embededStatus = nil
|
self.embededStatus = nil
|
||||||
self.mode = .new
|
self.mode = .new(vivibilty: visibility)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Models
|
||||||
extension StatusEditorViewModel {
|
extension StatusEditorViewModel {
|
||||||
public enum Mode {
|
public enum Mode {
|
||||||
case replyTo(status: Status)
|
case replyTo(status: Status)
|
||||||
case new
|
case new(vivibilty: Visibility)
|
||||||
case edit(status: Status)
|
case edit(status: Status)
|
||||||
case quote(status: Status)
|
case quote(status: Status)
|
||||||
case mention(account: Account, visibility: Visibility)
|
case mention(account: Account, visibility: Visibility)
|
||||||
|
|
|
@ -78,6 +78,7 @@ struct StatusActionsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderless)
|
.buttonStyle(.borderless)
|
||||||
|
.disabled(action == .boost && viewModel.status.visibility == .direct)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue