add conversation screens
This commit is contained in:
parent
57793daf3d
commit
9d6f610b63
|
@ -142,6 +142,20 @@
|
||||||
7E71758D2CECC5C70059F30B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7E71758B2CECC5C70059F30B /* Localizable.strings */; };
|
7E71758D2CECC5C70059F30B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7E71758B2CECC5C70059F30B /* Localizable.strings */; };
|
||||||
7E71758E2CECC5C70059F30B /* server_features.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7E71758A2CECC5C70059F30B /* server_features.plist */; };
|
7E71758E2CECC5C70059F30B /* server_features.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7E71758A2CECC5C70059F30B /* server_features.plist */; };
|
||||||
7E71758F2CECC5C70059F30B /* launchscreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7E7175892CECC5C70059F30B /* launchscreen.storyboard */; };
|
7E71758F2CECC5C70059F30B /* launchscreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7E7175892CECC5C70059F30B /* launchscreen.storyboard */; };
|
||||||
|
7E8442B02CF297E5001CEBD2 /* AttachmentPickerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8442A42CF297E5001CEBD2 /* AttachmentPickerScreen.swift */; };
|
||||||
|
7E8442B12CF297E5001CEBD2 /* ConversationMessageRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8442AB2CF297E5001CEBD2 /* ConversationMessageRow.swift */; };
|
||||||
|
7E8442B22CF297E5001CEBD2 /* CameraCellPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E84429F2CF297E5001CEBD2 /* CameraCellPreview.swift */; };
|
||||||
|
7E8442B32CF297E5001CEBD2 /* ConversationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8442AC2CF297E5001CEBD2 /* ConversationScreen.swift */; };
|
||||||
|
7E8442B42CF297E5001CEBD2 /* MediaPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8442A82CF297E5001CEBD2 /* MediaPickerView.swift */; };
|
||||||
|
7E8442B52CF297E5001CEBD2 /* ConversationTextInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8442AE2CF297E5001CEBD2 /* ConversationTextInput.swift */; };
|
||||||
|
7E8442B62CF297E5001CEBD2 /* ConversationSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8442AD2CF297E5001CEBD2 /* ConversationSettingsScreen.swift */; };
|
||||||
|
7E8442B72CF297E5001CEBD2 /* CameraPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8442A02CF297E5001CEBD2 /* CameraPicker.swift */; };
|
||||||
|
7E8442B82CF297E5001CEBD2 /* ConversationMessageContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8442AA2CF297E5001CEBD2 /* ConversationMessageContainer.swift */; };
|
||||||
|
7E8442B92CF297E5001CEBD2 /* ContactsPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8442A52CF297E5001CEBD2 /* ContactsPickerView.swift */; };
|
||||||
|
7E8442BA2CF297E5001CEBD2 /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8442A12CF297E5001CEBD2 /* CameraView.swift */; };
|
||||||
|
7E8442BB2CF297E5001CEBD2 /* GalleryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8442A22CF297E5001CEBD2 /* GalleryView.swift */; };
|
||||||
|
7E8442BC2CF297E5001CEBD2 /* LocationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8442A72CF297E5001CEBD2 /* LocationPickerView.swift */; };
|
||||||
|
7E8442BD2CF297E5001CEBD2 /* FilesPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8442A62CF297E5001CEBD2 /* FilesPickerView.swift */; };
|
||||||
7E8D7AE32CECD011009AD3DF /* SwiftfulRouting in Frameworks */ = {isa = PBXBuildFile; productRef = 7E8D7AE22CECD011009AD3DF /* SwiftfulRouting */; };
|
7E8D7AE32CECD011009AD3DF /* SwiftfulRouting in Frameworks */ = {isa = PBXBuildFile; productRef = 7E8D7AE22CECD011009AD3DF /* SwiftfulRouting */; };
|
||||||
7E8D7AF12CECEB30009AD3DF /* Images+Generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8D7AEF2CECEB30009AD3DF /* Images+Generated.swift */; };
|
7E8D7AF12CECEB30009AD3DF /* Images+Generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8D7AEF2CECEB30009AD3DF /* Images+Generated.swift */; };
|
||||||
7E8D7AF22CECEB30009AD3DF /* Strings+Generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8D7AF02CECEB30009AD3DF /* Strings+Generated.swift */; };
|
7E8D7AF22CECEB30009AD3DF /* Strings+Generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8D7AF02CECEB30009AD3DF /* Strings+Generated.swift */; };
|
||||||
|
@ -650,6 +664,20 @@
|
||||||
7E7175892CECC5C70059F30B /* launchscreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = launchscreen.storyboard; sourceTree = "<group>"; };
|
7E7175892CECC5C70059F30B /* launchscreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = launchscreen.storyboard; sourceTree = "<group>"; };
|
||||||
7E71758A2CECC5C70059F30B /* server_features.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = server_features.plist; sourceTree = "<group>"; };
|
7E71758A2CECC5C70059F30B /* server_features.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = server_features.plist; sourceTree = "<group>"; };
|
||||||
7E71758B2CECC5C70059F30B /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
|
7E71758B2CECC5C70059F30B /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
|
||||||
|
7E84429F2CF297E5001CEBD2 /* CameraCellPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraCellPreview.swift; sourceTree = "<group>"; };
|
||||||
|
7E8442A02CF297E5001CEBD2 /* CameraPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPicker.swift; sourceTree = "<group>"; };
|
||||||
|
7E8442A12CF297E5001CEBD2 /* CameraView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = "<group>"; };
|
||||||
|
7E8442A22CF297E5001CEBD2 /* GalleryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryView.swift; sourceTree = "<group>"; };
|
||||||
|
7E8442A42CF297E5001CEBD2 /* AttachmentPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentPickerScreen.swift; sourceTree = "<group>"; };
|
||||||
|
7E8442A52CF297E5001CEBD2 /* ContactsPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsPickerView.swift; sourceTree = "<group>"; };
|
||||||
|
7E8442A62CF297E5001CEBD2 /* FilesPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesPickerView.swift; sourceTree = "<group>"; };
|
||||||
|
7E8442A72CF297E5001CEBD2 /* LocationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPickerView.swift; sourceTree = "<group>"; };
|
||||||
|
7E8442A82CF297E5001CEBD2 /* MediaPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPickerView.swift; sourceTree = "<group>"; };
|
||||||
|
7E8442AA2CF297E5001CEBD2 /* ConversationMessageContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMessageContainer.swift; sourceTree = "<group>"; };
|
||||||
|
7E8442AB2CF297E5001CEBD2 /* ConversationMessageRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMessageRow.swift; sourceTree = "<group>"; };
|
||||||
|
7E8442AC2CF297E5001CEBD2 /* ConversationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationScreen.swift; sourceTree = "<group>"; };
|
||||||
|
7E8442AD2CF297E5001CEBD2 /* ConversationSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSettingsScreen.swift; sourceTree = "<group>"; };
|
||||||
|
7E8442AE2CF297E5001CEBD2 /* ConversationTextInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTextInput.swift; sourceTree = "<group>"; };
|
||||||
7E8D7AEE2CECEB30009AD3DF /* Colors+Generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Colors+Generated.swift"; sourceTree = "<group>"; };
|
7E8D7AEE2CECEB30009AD3DF /* Colors+Generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Colors+Generated.swift"; sourceTree = "<group>"; };
|
||||||
7E8D7AEF2CECEB30009AD3DF /* Images+Generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Images+Generated.swift"; sourceTree = "<group>"; };
|
7E8D7AEF2CECEB30009AD3DF /* Images+Generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Images+Generated.swift"; sourceTree = "<group>"; };
|
||||||
7E8D7AF02CECEB30009AD3DF /* Strings+Generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+Generated.swift"; sourceTree = "<group>"; };
|
7E8D7AF02CECEB30009AD3DF /* Strings+Generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+Generated.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -1489,10 +1517,48 @@
|
||||||
path = Strings;
|
path = Strings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
7E8442A32CF297E5001CEBD2 /* MediaPickerComponents */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
7E84429F2CF297E5001CEBD2 /* CameraCellPreview.swift */,
|
||||||
|
7E8442A02CF297E5001CEBD2 /* CameraPicker.swift */,
|
||||||
|
7E8442A12CF297E5001CEBD2 /* CameraView.swift */,
|
||||||
|
7E8442A22CF297E5001CEBD2 /* GalleryView.swift */,
|
||||||
|
);
|
||||||
|
path = MediaPickerComponents;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
7E8442A92CF297E5001CEBD2 /* Attachments */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
7E8442A32CF297E5001CEBD2 /* MediaPickerComponents */,
|
||||||
|
7E8442A42CF297E5001CEBD2 /* AttachmentPickerScreen.swift */,
|
||||||
|
7E8442A52CF297E5001CEBD2 /* ContactsPickerView.swift */,
|
||||||
|
7E8442A62CF297E5001CEBD2 /* FilesPickerView.swift */,
|
||||||
|
7E8442A72CF297E5001CEBD2 /* LocationPickerView.swift */,
|
||||||
|
7E8442A82CF297E5001CEBD2 /* MediaPickerView.swift */,
|
||||||
|
);
|
||||||
|
path = Attachments;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
7E8442AF2CF297E5001CEBD2 /* Conversation */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
7E8442A92CF297E5001CEBD2 /* Attachments */,
|
||||||
|
7E8442AA2CF297E5001CEBD2 /* ConversationMessageContainer.swift */,
|
||||||
|
7E8442AB2CF297E5001CEBD2 /* ConversationMessageRow.swift */,
|
||||||
|
7E8442AC2CF297E5001CEBD2 /* ConversationScreen.swift */,
|
||||||
|
7E8442AD2CF297E5001CEBD2 /* ConversationSettingsScreen.swift */,
|
||||||
|
7E8442AE2CF297E5001CEBD2 /* ConversationTextInput.swift */,
|
||||||
|
);
|
||||||
|
path = Conversation;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
7E8D7AE42CECD037009AD3DF /* Views */ = {
|
7E8D7AE42CECD037009AD3DF /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7E995F222CEAC5D2005B30EE /* RootView.swift */,
|
7E995F222CEAC5D2005B30EE /* RootView.swift */,
|
||||||
|
7E8442AF2CF297E5001CEBD2 /* Conversation */,
|
||||||
7E8D7AF92CECEDB3009AD3DF /* SharedComponents */,
|
7E8D7AF92CECEDB3009AD3DF /* SharedComponents */,
|
||||||
54D8CBD8978DA29C88226FBB /* Enter */,
|
54D8CBD8978DA29C88226FBB /* Enter */,
|
||||||
D7FD95FF8F72ECE4DBEE1095 /* Main */,
|
D7FD95FF8F72ECE4DBEE1095 /* Main */,
|
||||||
|
@ -2656,6 +2722,20 @@
|
||||||
7E8D7B212CECEE79009AD3DF /* Map+Extensions.swift in Sources */,
|
7E8D7B212CECEE79009AD3DF /* Map+Extensions.swift in Sources */,
|
||||||
7E8D7B222CECEE79009AD3DF /* View+Flip.swift in Sources */,
|
7E8D7B222CECEE79009AD3DF /* View+Flip.swift in Sources */,
|
||||||
7E8D7B232CECEE79009AD3DF /* View+TappableArea.swift in Sources */,
|
7E8D7B232CECEE79009AD3DF /* View+TappableArea.swift in Sources */,
|
||||||
|
7E8442B02CF297E5001CEBD2 /* AttachmentPickerScreen.swift in Sources */,
|
||||||
|
7E8442B12CF297E5001CEBD2 /* ConversationMessageRow.swift in Sources */,
|
||||||
|
7E8442B22CF297E5001CEBD2 /* CameraCellPreview.swift in Sources */,
|
||||||
|
7E8442B32CF297E5001CEBD2 /* ConversationScreen.swift in Sources */,
|
||||||
|
7E8442B42CF297E5001CEBD2 /* MediaPickerView.swift in Sources */,
|
||||||
|
7E8442B52CF297E5001CEBD2 /* ConversationTextInput.swift in Sources */,
|
||||||
|
7E8442B62CF297E5001CEBD2 /* ConversationSettingsScreen.swift in Sources */,
|
||||||
|
7E8442B72CF297E5001CEBD2 /* CameraPicker.swift in Sources */,
|
||||||
|
7E8442B82CF297E5001CEBD2 /* ConversationMessageContainer.swift in Sources */,
|
||||||
|
7E8442B92CF297E5001CEBD2 /* ContactsPickerView.swift in Sources */,
|
||||||
|
7E8442BA2CF297E5001CEBD2 /* CameraView.swift in Sources */,
|
||||||
|
7E8442BB2CF297E5001CEBD2 /* GalleryView.swift in Sources */,
|
||||||
|
7E8442BC2CF297E5001CEBD2 /* LocationPickerView.swift in Sources */,
|
||||||
|
7E8442BD2CF297E5001CEBD2 /* FilesPickerView.swift in Sources */,
|
||||||
7E8D7B242CECEE79009AD3DF /* URL+Extensions.swift in Sources */,
|
7E8D7B242CECEE79009AD3DF /* URL+Extensions.swift in Sources */,
|
||||||
7E8D7B252CECEE79009AD3DF /* PHImageManager+Fetch.swift in Sources */,
|
7E8D7B252CECEE79009AD3DF /* PHImageManager+Fetch.swift in Sources */,
|
||||||
7E8D7B262CECEE79009AD3DF /* View+If.swift in Sources */,
|
7E8D7B262CECEE79009AD3DF /* View+If.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
enum AttachmentTab: Int, CaseIterable {
|
||||||
|
case media
|
||||||
|
case files
|
||||||
|
case location
|
||||||
|
case contacts
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AttachmentPickerScreen: View {
|
||||||
|
@Environment(\.router) var router
|
||||||
|
|
||||||
|
@State private var selectedTab: AttachmentTab = .media
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
// Background color
|
||||||
|
Color.Material.Background.light
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
// Content
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
// Header
|
||||||
|
SharedNavigationBar(
|
||||||
|
leftButton: .init(
|
||||||
|
image: Image(systemName: "xmark"),
|
||||||
|
action: {
|
||||||
|
router.dismissScreen()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
centerText: .init(text: L10n.Attachment.Prompt.main)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pickers
|
||||||
|
switch selectedTab {
|
||||||
|
case .media:
|
||||||
|
MediaPickerView()
|
||||||
|
|
||||||
|
case .files:
|
||||||
|
FilesPickerView()
|
||||||
|
|
||||||
|
case .location:
|
||||||
|
LocationPickerView()
|
||||||
|
|
||||||
|
case .contacts:
|
||||||
|
ContactsPickerView()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab bar
|
||||||
|
AttachmentTabBar(selectedTab: $selectedTab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AttachmentTabBar: View {
|
||||||
|
@Binding var selectedTab: AttachmentTab
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Rectangle()
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: 0.2)
|
||||||
|
.foregroundColor(.Material.Shape.separator)
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
AttachmentTabBarButton(tab: .media, selected: $selectedTab)
|
||||||
|
AttachmentTabBarButton(tab: .files, selected: $selectedTab)
|
||||||
|
AttachmentTabBarButton(tab: .location, selected: $selectedTab)
|
||||||
|
AttachmentTabBarButton(tab: .contacts, selected: $selectedTab)
|
||||||
|
}
|
||||||
|
.background(Color.Material.Background.dark)
|
||||||
|
}
|
||||||
|
.frame(height: 50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct AttachmentTabBarButton: View {
|
||||||
|
let tab: AttachmentTab
|
||||||
|
@Binding var selected: AttachmentTab
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
VStack(spacing: 2) {
|
||||||
|
buttonImg
|
||||||
|
.foregroundColor(selected == tab ? .Material.Elements.active : .Material.Elements.inactive)
|
||||||
|
.font(.system(size: 24, weight: .light))
|
||||||
|
.symbolRenderingMode(.hierarchical)
|
||||||
|
Text(buttonTitle)
|
||||||
|
.font(.sub1)
|
||||||
|
.foregroundColor(selected == tab ? .Material.Text.main : .Material.Elements.inactive)
|
||||||
|
}
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(.white.opacity(0.01))
|
||||||
|
.onTapGesture {
|
||||||
|
selected = tab
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonImg: Image {
|
||||||
|
switch tab {
|
||||||
|
case .media:
|
||||||
|
return Image(systemName: "photo.on.rectangle.angled")
|
||||||
|
|
||||||
|
case .files:
|
||||||
|
return Image(systemName: "doc.on.doc")
|
||||||
|
|
||||||
|
case .location:
|
||||||
|
return Image(systemName: "location.circle")
|
||||||
|
|
||||||
|
case .contacts:
|
||||||
|
return Image(systemName: "person.crop.circle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonTitle: String {
|
||||||
|
switch tab {
|
||||||
|
case .media:
|
||||||
|
return L10n.Attachment.Tab.media
|
||||||
|
|
||||||
|
case .files:
|
||||||
|
return L10n.Attachment.Tab.files
|
||||||
|
|
||||||
|
case .location:
|
||||||
|
return L10n.Attachment.Tab.location
|
||||||
|
|
||||||
|
case .contacts:
|
||||||
|
return L10n.Attachment.Tab.contacts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ContactsPickerView: View {
|
||||||
|
@Environment(\.router) var router
|
||||||
|
// @EnvironmentObject var messages: MessagesStore
|
||||||
|
|
||||||
|
// @State private var rosters: [Roster] = []
|
||||||
|
// @State private var selectedContact: Roster?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Text("dumb")
|
||||||
|
// VStack(spacing: 0) {
|
||||||
|
// // Contacts list
|
||||||
|
// if !rosters.isEmpty {
|
||||||
|
// List {
|
||||||
|
// ForEach(rosters) { roster in
|
||||||
|
// ContactRow(roster: roster, selectedContact: $selectedContact)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .listStyle(.plain)
|
||||||
|
// .background(Color.Material.Background.light)
|
||||||
|
// } else {
|
||||||
|
// Spacer()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Send panel
|
||||||
|
// Rectangle()
|
||||||
|
// .foregroundColor(.Material.Shape.black)
|
||||||
|
// .frame(maxWidth: .infinity)
|
||||||
|
// .frame(height: selectedContact == nil ? 0 : 50)
|
||||||
|
// .overlay {
|
||||||
|
// HStack {
|
||||||
|
// Text(L10n.Attachment.Send.contact)
|
||||||
|
// .foregroundColor(.Material.Text.white)
|
||||||
|
// .font(.body1)
|
||||||
|
// Image(systemName: "arrow.up.circle")
|
||||||
|
// .foregroundColor(.Material.Text.white)
|
||||||
|
// .font(.body1)
|
||||||
|
// .padding(.leading, 8)
|
||||||
|
// }
|
||||||
|
// .padding()
|
||||||
|
// }
|
||||||
|
// .clipped()
|
||||||
|
// .onTapGesture {
|
||||||
|
// if let selectedContact = selectedContact {
|
||||||
|
// messages.sendContact(selectedContact.contactBareJid)
|
||||||
|
// router.dismissEnvironment()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .task {
|
||||||
|
// rosters = await Roster.allActive
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// private struct ContactRow: View {
|
||||||
|
// var roster: Roster
|
||||||
|
// @Binding var selectedContact: Roster?
|
||||||
|
//
|
||||||
|
// var body: some View {
|
||||||
|
// SharedListRow(
|
||||||
|
// iconType: .charCircle(roster.name?.firstLetter ?? roster.contactBareJid.firstLetter),
|
||||||
|
// text: roster.contactBareJid,
|
||||||
|
// controlType: .none
|
||||||
|
// )
|
||||||
|
// .onTapGesture {
|
||||||
|
// selectedContact = roster
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
|
@ -0,0 +1,66 @@
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
struct FilesPickerView: View {
|
||||||
|
@Environment(\.router) var router
|
||||||
|
// @EnvironmentObject var attachments: AttachmentsStore
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Text("dumb")
|
||||||
|
// DocumentPicker(
|
||||||
|
// completion: { dataArray, extensionsArray in
|
||||||
|
// attachments.sendDocuments(dataArray, extensionsArray)
|
||||||
|
// router.dismissEnvironment()
|
||||||
|
// },
|
||||||
|
// cancel: {
|
||||||
|
// router.dismissEnvironment()
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DocumentPicker: UIViewControllerRepresentable {
|
||||||
|
let completion: ([Data], [String]) -> Void
|
||||||
|
let cancel: () -> Void
|
||||||
|
|
||||||
|
func makeUIViewController(context: UIViewControllerRepresentableContext<DocumentPicker>) -> UIDocumentPickerViewController {
|
||||||
|
let picker: UIDocumentPickerViewController
|
||||||
|
picker = UIDocumentPickerViewController(forOpeningContentTypes: [.item], asCopy: true)
|
||||||
|
picker.delegate = context.coordinator
|
||||||
|
picker.allowsMultipleSelection = true
|
||||||
|
return picker
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_: UIDocumentPickerViewController, context _: UIViewControllerRepresentableContext<DocumentPicker>) {}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Coordinator: NSObject, UIDocumentPickerDelegate {
|
||||||
|
var parent: DocumentPicker
|
||||||
|
|
||||||
|
init(_ parent: DocumentPicker) {
|
||||||
|
self.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
func documentPicker(_: UIDocumentPickerViewController, didPickDocumentsAt: [URL]) {
|
||||||
|
var dataArray = [Data]()
|
||||||
|
var extensionArray = [String]()
|
||||||
|
for url in didPickDocumentsAt {
|
||||||
|
do {
|
||||||
|
let data = try Data(contentsOf: url)
|
||||||
|
dataArray.append(data)
|
||||||
|
extensionArray.append(url.pathExtension)
|
||||||
|
} catch {
|
||||||
|
print("Unable to load data from \(url): \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parent.completion(dataArray, extensionArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
func documentPickerWasCancelled(_: UIDocumentPickerViewController) {
|
||||||
|
parent.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
import MapKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LocationPickerView: View {
|
||||||
|
@Environment(\.router) var router
|
||||||
|
// @EnvironmentObject var messages: MessagesStore
|
||||||
|
|
||||||
|
@StateObject var locationManager = LocationManager()
|
||||||
|
@State private var region = MKCoordinateRegion()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
ZStack {
|
||||||
|
// MapView
|
||||||
|
MapView(region: $region)
|
||||||
|
.onAppear {
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
|
self.region = locationManager.region
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.overlay {
|
||||||
|
Image(systemName: "mappin")
|
||||||
|
.foregroundColor(.Material.Elements.active)
|
||||||
|
.font(.system(size: 30))
|
||||||
|
.shadow(color: .white, radius: 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track button
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "location.circle")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 40, height: 40)
|
||||||
|
.foregroundColor(.Material.Elements.active)
|
||||||
|
.background(Color.Material.Shape.white)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.shadow(color: .white, radius: 2)
|
||||||
|
.padding(.trailing)
|
||||||
|
.padding(.bottom, 50)
|
||||||
|
.tappablePadding(.symmetric(10)) {
|
||||||
|
self.region = locationManager.region
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send panel
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(.Material.Shape.black)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: 50)
|
||||||
|
.overlay {
|
||||||
|
HStack {
|
||||||
|
Text(L10n.Attachment.Send.location)
|
||||||
|
.foregroundColor(.Material.Text.white)
|
||||||
|
.font(.body1)
|
||||||
|
Image(systemName: "arrow.up.circle")
|
||||||
|
.foregroundColor(.Material.Text.white)
|
||||||
|
.font(.body1)
|
||||||
|
.padding(.leading, 8)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.clipped()
|
||||||
|
.onTapGesture {
|
||||||
|
// messages.sendLocation(region.center.latitude, region.center.longitude)
|
||||||
|
router.dismissEnvironment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
locationManager.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MapView: UIViewRepresentable {
|
||||||
|
@Binding var region: MKCoordinateRegion
|
||||||
|
|
||||||
|
func makeUIView(context: Context) -> MKMapView {
|
||||||
|
let mapView = MKMapView()
|
||||||
|
mapView.delegate = context.coordinator
|
||||||
|
mapView.showsUserLocation = false
|
||||||
|
mapView.userTrackingMode = .none
|
||||||
|
|
||||||
|
return mapView
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: MKMapView, context _: Context) {
|
||||||
|
if uiView.region != region {
|
||||||
|
uiView.setRegion(region, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Coordinator: NSObject, MKMapViewDelegate {
|
||||||
|
var parent: MapView
|
||||||
|
|
||||||
|
init(_ parent: MapView) {
|
||||||
|
self.parent = parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
|
||||||
|
private let locationManager = CLLocationManager()
|
||||||
|
@Published var region: MKCoordinateRegion
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
region = MKCoordinateRegion()
|
||||||
|
super.init()
|
||||||
|
locationManager.delegate = self
|
||||||
|
locationManager.desiredAccuracy = kCLLocationAccuracyBest
|
||||||
|
}
|
||||||
|
|
||||||
|
func start() {
|
||||||
|
locationManager.requestWhenInUseAuthorization()
|
||||||
|
locationManager.startUpdatingLocation()
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop() {
|
||||||
|
locationManager.stopUpdatingLocation()
|
||||||
|
}
|
||||||
|
|
||||||
|
func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||||
|
if let loc = locations.first {
|
||||||
|
region = MKCoordinateRegion(
|
||||||
|
center: loc.coordinate,
|
||||||
|
span: MKCoordinateSpan(latitudeDelta: 0.002, longitudeDelta: 0.002)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
import AVFoundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CameraCellPreview: View {
|
||||||
|
@Environment(\.router) var router
|
||||||
|
// @EnvironmentObject var attachments: AttachmentsStore
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Text("dumb")
|
||||||
|
// Group {
|
||||||
|
// if attachments.cameraAccessGranted {
|
||||||
|
// ZStack {
|
||||||
|
// CameraView()
|
||||||
|
// .aspectRatio(1, contentMode: .fit)
|
||||||
|
// .frame(maxWidth: .infinity)
|
||||||
|
// Image(systemName: "camera")
|
||||||
|
// .resizable()
|
||||||
|
// .aspectRatio(contentMode: .fit)
|
||||||
|
// .frame(width: 40, height: 40)
|
||||||
|
// .foregroundColor(.white)
|
||||||
|
// .padding(8)
|
||||||
|
// .background(Color.black.opacity(0.5))
|
||||||
|
// .clipShape(Circle())
|
||||||
|
// .padding(8)
|
||||||
|
// }
|
||||||
|
// .onTapGesture {
|
||||||
|
// router.showScreen(.fullScreenCover) { _ in
|
||||||
|
// CameraPicker { data, type in
|
||||||
|
// attachments.sendCaptured(data, type)
|
||||||
|
// router.dismissEnvironment()
|
||||||
|
// }
|
||||||
|
// .ignoresSafeArea(.all)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// Button {
|
||||||
|
// openAppSettings()
|
||||||
|
// } label: {
|
||||||
|
// ZStack {
|
||||||
|
// Rectangle()
|
||||||
|
// .fill(Color.Material.Background.light)
|
||||||
|
// .overlay {
|
||||||
|
// VStack {
|
||||||
|
// Image(systemName: "camera")
|
||||||
|
// .foregroundColor(.Material.Elements.active)
|
||||||
|
// .font(.system(size: 30))
|
||||||
|
// Text("Allow camera access")
|
||||||
|
// .foregroundColor(.Material.Text.main)
|
||||||
|
// .font(.body3)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .frame(height: 100)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .task {
|
||||||
|
// await attachments.checkCameraAuthorization()
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import Foundation
|
||||||
|
import Photos
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CameraPicker: UIViewControllerRepresentable {
|
||||||
|
// var completionHandler: (Data, GalleryMediaType) -> Void
|
||||||
|
|
||||||
|
func makeUIViewController(context: Context) -> UIImagePickerController {
|
||||||
|
let picker = UIImagePickerController()
|
||||||
|
picker.sourceType = .camera
|
||||||
|
picker.delegate = context.coordinator
|
||||||
|
picker.mediaTypes = [UTType.movie.identifier, UTType.image.identifier]
|
||||||
|
picker.videoQuality = .typeHigh
|
||||||
|
picker.videoMaximumDuration = Const.videoDurationLimit
|
||||||
|
picker.view.backgroundColor = .clear
|
||||||
|
return picker
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_: UIImagePickerController, context _: Context) {}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
|
||||||
|
let parent: CameraPicker
|
||||||
|
|
||||||
|
init(_ parent: CameraPicker) {
|
||||||
|
self.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
func imagePickerController(_: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
|
||||||
|
// swiftlint:disable:next force_cast
|
||||||
|
let mediaType = info[.mediaType] as! String
|
||||||
|
|
||||||
|
// if mediaType == UTType.image.identifier {
|
||||||
|
// if let image = info[.originalImage] as? UIImage {
|
||||||
|
// let data = image.jpegData(compressionQuality: 1.0) ?? Data()
|
||||||
|
// parent.completionHandler(data, .photo)
|
||||||
|
// }
|
||||||
|
// } else if mediaType == UTType.movie.identifier {
|
||||||
|
// if let url = info[.mediaURL] as? URL {
|
||||||
|
// let data = try? Data(contentsOf: url)
|
||||||
|
// parent.completionHandler(data ?? Data(), .video)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import AVFoundation
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class CameraUIView: UIView {
|
||||||
|
var previewLayer: AVCaptureVideoPreviewLayer?
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
previewLayer?.frame = bounds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CameraView: UIViewRepresentable {
|
||||||
|
func makeUIView(context _: Context) -> CameraUIView {
|
||||||
|
let view = CameraUIView()
|
||||||
|
|
||||||
|
let captureSession = AVCaptureSession()
|
||||||
|
guard let captureDevice = AVCaptureDevice.default(for: .video) else { return view }
|
||||||
|
guard let input = try? AVCaptureDeviceInput(device: captureDevice) else { return view }
|
||||||
|
captureSession.addInput(input)
|
||||||
|
|
||||||
|
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
|
||||||
|
previewLayer.videoGravity = .resizeAspectFill
|
||||||
|
view.layer.addSublayer(previewLayer)
|
||||||
|
view.previewLayer = previewLayer
|
||||||
|
|
||||||
|
DispatchQueue.global(qos: .background).async {
|
||||||
|
captureSession.startRunning()
|
||||||
|
}
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: CameraUIView, context _: Context) {
|
||||||
|
uiView.previewLayer?.frame = uiView.bounds
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct GalleryView: View {
|
||||||
|
// @EnvironmentObject var attachments: AttachmentsStore
|
||||||
|
@Binding var selectedItems: [String]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Text("dumb")
|
||||||
|
// Group {
|
||||||
|
// if attachments.galleryAccessGranted {
|
||||||
|
// ForEach(attachments.galleryItems) { item in
|
||||||
|
// GridViewItem(item: item, selected: $selectedItems)
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// Button {
|
||||||
|
// openAppSettings()
|
||||||
|
// } label: {
|
||||||
|
// ZStack {
|
||||||
|
// Rectangle()
|
||||||
|
// .fill(Color.Material.Background.light)
|
||||||
|
// .overlay {
|
||||||
|
// VStack {
|
||||||
|
// Image(systemName: "photo")
|
||||||
|
// .foregroundColor(.Material.Elements.active)
|
||||||
|
// .font(.system(size: 30))
|
||||||
|
// Text("Allow gallery access")
|
||||||
|
// .foregroundColor(.Material.Text.main)
|
||||||
|
// .font(.body3)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .frame(height: 100)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .task {
|
||||||
|
// await attachments.checkGalleryAuthorization()
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct GridViewItem: View {
|
||||||
|
// @State var item: GalleryItem
|
||||||
|
// @Binding var selected: [String]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Text("dumb")
|
||||||
|
// if let img = item.thumbnail {
|
||||||
|
// ZStack {
|
||||||
|
// img
|
||||||
|
// .resizable()
|
||||||
|
// .aspectRatio(contentMode: .fill)
|
||||||
|
// .frame(width: Const.galleryGridSize, height: Const.galleryGridSize)
|
||||||
|
// .clipped()
|
||||||
|
// if let duration = item.duration {
|
||||||
|
// VStack {
|
||||||
|
// Spacer()
|
||||||
|
// HStack {
|
||||||
|
// Spacer()
|
||||||
|
// Text(duration)
|
||||||
|
// .foregroundColor(.Material.Text.white)
|
||||||
|
// .font(.sub1)
|
||||||
|
// .shadow(color: .black, radius: 2)
|
||||||
|
// .padding(4)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if isSelected {
|
||||||
|
// VStack {
|
||||||
|
// HStack {
|
||||||
|
// Spacer()
|
||||||
|
// Circle()
|
||||||
|
// .frame(width: 30, height: 30)
|
||||||
|
// .shadow(color: .black, radius: 2)
|
||||||
|
// .foregroundColor(.Material.Shape.white)
|
||||||
|
// .overlay {
|
||||||
|
// Image(systemName: "checkmark")
|
||||||
|
// .foregroundColor(.Material.Elements.active)
|
||||||
|
// .font(.body3)
|
||||||
|
// }
|
||||||
|
// .padding(4)
|
||||||
|
// }
|
||||||
|
// Spacer()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .onTapGesture {
|
||||||
|
// if isSelected {
|
||||||
|
// selected.removeAll { $0 == item.id }
|
||||||
|
// } else {
|
||||||
|
// selected.append(item.id)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// ZStack {
|
||||||
|
// Rectangle()
|
||||||
|
// .fill(Color.Material.Background.light)
|
||||||
|
// .overlay {
|
||||||
|
// ProgressView()
|
||||||
|
// .foregroundColor(.Material.Elements.active)
|
||||||
|
// }
|
||||||
|
// .frame(width: Const.galleryGridSize, height: Const.galleryGridSize)
|
||||||
|
// }
|
||||||
|
// .task {
|
||||||
|
// if item.thumbnail == nil {
|
||||||
|
// try? await item.fetchThumbnail()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isSelected: Bool {
|
||||||
|
false
|
||||||
|
// selected.contains(item.id)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
import AVFoundation
|
||||||
|
import MobileCoreServices
|
||||||
|
import Photos
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MediaPickerView: View {
|
||||||
|
@Environment(\.router) var router
|
||||||
|
// @EnvironmentObject var attachments: AttachmentsStore
|
||||||
|
|
||||||
|
@State private var selectedItems: [String] = []
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
let columns = Array(repeating: GridItem(.flexible(), spacing: 0), count: 3)
|
||||||
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
// List of media
|
||||||
|
ScrollView(showsIndicators: false) {
|
||||||
|
LazyVGrid(columns: columns, spacing: 0) {
|
||||||
|
// For camera
|
||||||
|
CameraCellPreview()
|
||||||
|
|
||||||
|
// For gallery
|
||||||
|
GalleryView(selectedItems: $selectedItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send panel
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(.Material.Shape.black)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: self.selectedItems.isEmpty ? 0 : 50)
|
||||||
|
.overlay {
|
||||||
|
HStack {
|
||||||
|
Text(L10n.Attachment.Send.media)
|
||||||
|
.foregroundColor(.Material.Text.white)
|
||||||
|
.font(.body1)
|
||||||
|
Image(systemName: "arrow.up.circle")
|
||||||
|
.foregroundColor(.Material.Text.white)
|
||||||
|
.font(.body1)
|
||||||
|
.padding(.leading, 8)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.clipped()
|
||||||
|
.onTapGesture {
|
||||||
|
// let items = attachments.galleryItems.filter { selectedItems.contains($0.id) }
|
||||||
|
// attachments.sendMedia(items)
|
||||||
|
router.dismissEnvironment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,263 @@
|
||||||
|
import AVKit
|
||||||
|
import MapKit
|
||||||
|
import QuickLook
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ConversationMessageContainer: View {
|
||||||
|
// let message: Message
|
||||||
|
let isOutgoing: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Text("dumb")
|
||||||
|
// if let msgText = message.body, msgText.isLocation {
|
||||||
|
// EmbededMapView(location: msgText.getLatLon)
|
||||||
|
// } else if let msgText = message.body, msgText.isContact {
|
||||||
|
// ContactView(message: message)
|
||||||
|
// } else if case .attachment(let attachment) = message.contentType {
|
||||||
|
// AttachmentView(message: message, attachment: attachment)
|
||||||
|
// } else {
|
||||||
|
// Text(message.body ?? "...")
|
||||||
|
// .font(.body2)
|
||||||
|
// .foregroundColor(.Material.Text.main)
|
||||||
|
// .multilineTextAlignment(.leading)
|
||||||
|
// .padding(10)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MessageAttr: View {
|
||||||
|
// let message: Message
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
// VStack(alignment: .leading, spacing: 0) {
|
||||||
|
// Text(message.date, style: .time)
|
||||||
|
// .font(.sub2)
|
||||||
|
// .foregroundColor(.Material.Shape.separator)
|
||||||
|
// Spacer()
|
||||||
|
// if message.status == .error {
|
||||||
|
// Image(systemName: "exclamationmark.circle")
|
||||||
|
// .font(.body3)
|
||||||
|
// .foregroundColor(.Rainbow.red500)
|
||||||
|
// } else if message.status == .pending {
|
||||||
|
// Image(systemName: "clock")
|
||||||
|
// .font(.body3)
|
||||||
|
// .foregroundColor(.Material.Shape.separator)
|
||||||
|
// } else if message.secure {
|
||||||
|
// Image(systemName: "lock")
|
||||||
|
// .font(.body3)
|
||||||
|
// .foregroundColor(.Material.Shape.separator)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
Text("dumb")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct EmbededMapView: View {
|
||||||
|
let location: CLLocationCoordinate2D
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Map(
|
||||||
|
coordinateRegion: .constant(MKCoordinateRegion(center: location, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))),
|
||||||
|
interactionModes: [],
|
||||||
|
showsUserLocation: false,
|
||||||
|
userTrackingMode: .none,
|
||||||
|
annotationItems: [location],
|
||||||
|
annotationContent: { _ in
|
||||||
|
MapMarker(coordinate: location, tint: .blue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.frame(width: Const.mapPreviewSize, height: Const.mapPreviewSize)
|
||||||
|
.onTapGesture {
|
||||||
|
let mapItem = MKMapItem(placemark: MKPlacemark(coordinate: location))
|
||||||
|
mapItem.name = "Location"
|
||||||
|
mapItem.openInMaps(launchOptions: [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ContactView: View {
|
||||||
|
// let message: Message
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
ZStack {
|
||||||
|
Circle()
|
||||||
|
.frame(width: 44, height: 44)
|
||||||
|
.foregroundColor(contactName.firstLetterColor)
|
||||||
|
Text(contactName.firstLetter)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.font(.body1)
|
||||||
|
}
|
||||||
|
// Text(message.body?.getContactJid ?? "...")
|
||||||
|
// .font(.body2)
|
||||||
|
// .foregroundColor(.Material.Text.main)
|
||||||
|
// .multilineTextAlignment(.leading)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.onTapGesture {
|
||||||
|
// TODO: Jump to add roster from here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var contactName: String {
|
||||||
|
"dumb"
|
||||||
|
// message.body?.getContactJid ?? "?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// private struct AttachmentView: View {
|
||||||
|
// @EnvironmentObject var attachments: AttachmentsStore
|
||||||
|
//
|
||||||
|
// let message: Message
|
||||||
|
// let attachment: Attachment
|
||||||
|
//
|
||||||
|
// var body: some View {
|
||||||
|
// if message.status == .error {
|
||||||
|
// failed
|
||||||
|
// } else {
|
||||||
|
// switch attachment.type {
|
||||||
|
// case .image:
|
||||||
|
// AsyncImage(url: attachment.thumbnailPath) { image in
|
||||||
|
// image
|
||||||
|
// .resizable()
|
||||||
|
// .aspectRatio(contentMode: .fit)
|
||||||
|
// .frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
|
||||||
|
// } placeholder: {
|
||||||
|
// placeholder
|
||||||
|
// }
|
||||||
|
// .frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
|
||||||
|
//
|
||||||
|
// case .video:
|
||||||
|
// if let file = attachment.localPath {
|
||||||
|
// VideoPlayerView(url: file)
|
||||||
|
// .frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
|
||||||
|
// } else {
|
||||||
|
// placeholder
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// case .file:
|
||||||
|
// if let file = attachment.localPath {
|
||||||
|
// DocumentPreview(url: file)
|
||||||
|
// .frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
|
||||||
|
// } else {
|
||||||
|
// placeholder
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// default:
|
||||||
|
// placeholder
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @ViewBuilder private var placeholder: some View {
|
||||||
|
// Rectangle()
|
||||||
|
// .foregroundColor(.Material.Background.dark)
|
||||||
|
// .frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
|
||||||
|
// .overlay {
|
||||||
|
// ZStack {
|
||||||
|
// ProgressView()
|
||||||
|
// .scaleEffect(1.5)
|
||||||
|
// .progressViewStyle(CircularProgressViewStyle(tint: .Material.Elements.active))
|
||||||
|
// let imageName = progressImageName(attachment.type)
|
||||||
|
// Image(systemName: imageName)
|
||||||
|
// .font(.body1)
|
||||||
|
// .foregroundColor(.Material.Elements.active)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @ViewBuilder private var failed: some View {
|
||||||
|
// Rectangle()
|
||||||
|
// .foregroundColor(.Material.Background.dark)
|
||||||
|
// .frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
|
||||||
|
// .overlay {
|
||||||
|
// ZStack {
|
||||||
|
// VStack {
|
||||||
|
// Text(L10n.Attachment.Downloading.retry)
|
||||||
|
// .font(.body3)
|
||||||
|
// .foregroundColor(.Rainbow.red500)
|
||||||
|
// Image(systemName: "exclamationmark.arrow.triangle.2.circlepath")
|
||||||
|
// .font(.body1)
|
||||||
|
// .foregroundColor(.Rainbow.red500)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .onTapGesture {
|
||||||
|
// Task {
|
||||||
|
// try? await message.setStatus(.pending)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private func progressImageName(_ type: AttachmentType) -> String {
|
||||||
|
// switch type {
|
||||||
|
// case .image:
|
||||||
|
// return "photo"
|
||||||
|
//
|
||||||
|
// case .audio:
|
||||||
|
// return "music.note"
|
||||||
|
//
|
||||||
|
// case .video:
|
||||||
|
// return "film"
|
||||||
|
//
|
||||||
|
// case .file:
|
||||||
|
// return "doc"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private func thumbnail() -> Image? {
|
||||||
|
// guard let thumbnailPath = attachment.thumbnailPath else { return nil }
|
||||||
|
// guard let uiImage = UIImage(contentsOfFile: thumbnailPath.path()) else { return nil }
|
||||||
|
// return Image(uiImage: uiImage)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO: Make video player better!
|
||||||
|
private struct VideoPlayerView: UIViewControllerRepresentable {
|
||||||
|
let url: URL
|
||||||
|
|
||||||
|
func makeUIViewController(context _: Context) -> AVPlayerViewController {
|
||||||
|
let controller = AVPlayerViewController()
|
||||||
|
controller.player = AVPlayer(url: url)
|
||||||
|
controller.allowsPictureInPicturePlayback = true
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_: AVPlayerViewController, context _: Context) {
|
||||||
|
// Update the controller if needed.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DocumentPreview: UIViewControllerRepresentable {
|
||||||
|
var url: URL
|
||||||
|
|
||||||
|
func makeUIViewController(context: Context) -> QLPreviewController {
|
||||||
|
let controller = QLPreviewController()
|
||||||
|
controller.dataSource = context.coordinator
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_: QLPreviewController, context _: Context) {
|
||||||
|
// Update the controller if needed.
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Coordinator: NSObject, QLPreviewControllerDataSource {
|
||||||
|
var parent: DocumentPreview
|
||||||
|
|
||||||
|
init(_ parent: DocumentPreview) {
|
||||||
|
self.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
func numberOfPreviewItems(in _: QLPreviewController) -> Int {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
func previewController(_: QLPreviewController, previewItemAt _: Int) -> QLPreviewItem {
|
||||||
|
parent.url as QLPreviewItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ConversationMessageRow: View {
|
||||||
|
// @EnvironmentObject var messages: MessagesStore
|
||||||
|
// let message: Message
|
||||||
|
@State private var offset: CGSize = .zero
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
if isOutgoing() {
|
||||||
|
Spacer()
|
||||||
|
// MessageAttr(message: message)
|
||||||
|
// .padding(.trailing, 4)
|
||||||
|
}
|
||||||
|
// ConversationMessageContainer(message: message, isOutgoing: isOutgoing())
|
||||||
|
// .background(isOutgoing() ? Color.Material.Shape.alternate : Color.Material.Shape.white)
|
||||||
|
// .clipShape(ConversationMessageBubble(isOutgoing: isOutgoing()))
|
||||||
|
if !isOutgoing() {
|
||||||
|
// MessageAttr(message: message)
|
||||||
|
// .padding(.leading, 4)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical, 10)
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
.background(Color.clearTappable)
|
||||||
|
.offset(offset)
|
||||||
|
.gesture(
|
||||||
|
DragGesture(minimumDistance: 30, coordinateSpace: .local)
|
||||||
|
.onChanged { value in
|
||||||
|
var width = value.translation.width
|
||||||
|
width = width > 0 ? 0 : width
|
||||||
|
offset = CGSize(width: width, height: 0)
|
||||||
|
}
|
||||||
|
.onEnded { value in
|
||||||
|
let targetWidth: CGFloat = -90
|
||||||
|
withAnimation(.easeOut(duration: 0.1)) {
|
||||||
|
if value.translation.width <= targetWidth {
|
||||||
|
Vibration.success.vibrate()
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||||
|
withAnimation(.easeOut(duration: 0.1)) {
|
||||||
|
offset = .zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offset = .zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value.translation.width <= targetWidth {
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.02) {
|
||||||
|
// messages.replyText = message.body ?? ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.listRowInsets(.zero)
|
||||||
|
.listRowSeparator(.hidden)
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
.background(Color.Material.Background.light)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isOutgoing() -> Bool {
|
||||||
|
true
|
||||||
|
// message.from == messages.roster.bareJid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConversationMessageBubble: Shape {
|
||||||
|
let isOutgoing: Bool
|
||||||
|
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
let path = UIBezierPath(
|
||||||
|
roundedRect: rect,
|
||||||
|
byRoundingCorners: isOutgoing ? [.topLeft, .bottomLeft, .bottomRight] : [.topRight, .bottomLeft, .bottomRight],
|
||||||
|
cornerRadii: CGSize(width: 8, height: 10)
|
||||||
|
)
|
||||||
|
return Path(path.cgPath)
|
||||||
|
}
|
||||||
|
}
|
126
Monal/another.im/Views/Conversation/ConversationScreen.swift
Normal file
126
Monal/another.im/Views/Conversation/ConversationScreen.swift
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ConversationScreen: View {
|
||||||
|
@Environment(\.router) var router
|
||||||
|
@EnvironmentObject var chatModel: ChatModel
|
||||||
|
// @StateObject var messagesStore: MessagesStore
|
||||||
|
// @StateObject var attachments: AttachmentsStore
|
||||||
|
// @StateObject var settings: ChatSettingsStore
|
||||||
|
|
||||||
|
@State private var autoScroll = true
|
||||||
|
@State private var firstIsVisible = true
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
// Background color
|
||||||
|
Color.Material.Background.light
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
// Content
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
// Header
|
||||||
|
SharedNavigationBar(
|
||||||
|
leftButton: .init(
|
||||||
|
image: Image(systemName: "chevron.left"),
|
||||||
|
action: {
|
||||||
|
router.dismissScreen()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
centerText: .init(text: centerText()),
|
||||||
|
rightButton: .init(
|
||||||
|
image: Image(systemName: "gear"),
|
||||||
|
action: {
|
||||||
|
router.showScreen(.push) { _ in
|
||||||
|
ConversationSettingsScreen()
|
||||||
|
// .environmentObject(settings)
|
||||||
|
.navigationBarHidden(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Msg list
|
||||||
|
// let messages = messagesStore.messages
|
||||||
|
// if !messages.isEmpty {
|
||||||
|
// ScrollViewReader { proxy in
|
||||||
|
// ScrollView {
|
||||||
|
// LazyVStack(spacing: 0) {
|
||||||
|
// ForEach(messages) { message in
|
||||||
|
// ConversationMessageRow(message: message)
|
||||||
|
// .id(message.id)
|
||||||
|
// .flip()
|
||||||
|
// .onAppear {
|
||||||
|
// if message.id == messages.first?.id {
|
||||||
|
// firstIsVisible = true
|
||||||
|
// autoScroll = true
|
||||||
|
// }
|
||||||
|
// messagesStore.scrolledMessage(message.id)
|
||||||
|
// }
|
||||||
|
// .onDisappear {
|
||||||
|
// if message.id == messages.first?.id {
|
||||||
|
// firstIsVisible = false
|
||||||
|
// autoScroll = false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .flip()
|
||||||
|
// .scrollDismissesKeyboard(.immediately)
|
||||||
|
// .onChange(of: autoScroll) { new in
|
||||||
|
// if new, !firstIsVisible {
|
||||||
|
// withAnimation {
|
||||||
|
// proxy.scrollTo(messages.first?.id, anchor: .top)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// Spacer()
|
||||||
|
// }
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jump to last button
|
||||||
|
if !autoScroll {
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Button {
|
||||||
|
autoScroll = true
|
||||||
|
} label: {
|
||||||
|
ZStack {
|
||||||
|
Circle()
|
||||||
|
.fill(Color.Material.Shape.white)
|
||||||
|
Image(systemName: "arrow.down")
|
||||||
|
.foregroundColor(.Material.Elements.active)
|
||||||
|
}
|
||||||
|
.frame(width: 40, height: 40)
|
||||||
|
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||||
|
.padding(.trailing, 8)
|
||||||
|
.padding(.bottom, 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// .environmentObject(messagesStore)
|
||||||
|
// .environmentObject(attachments)
|
||||||
|
// .safeAreaInset(edge: .bottom, spacing: 0) {
|
||||||
|
// ConversationTextInput(autoScroll: $autoScroll)
|
||||||
|
// .environmentObject(messagesStore)
|
||||||
|
// .environmentObject(attachments)
|
||||||
|
// .environmentObject(settings)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private func centerText() -> String {
|
||||||
|
chatModel.contact.name ?? chatModel.contact.contactJid
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ConversationSettingsScreen: View {
|
||||||
|
@Environment(\.router) var router
|
||||||
|
// @EnvironmentObject var settingsStore: ChatSettingsStore
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
// Background color
|
||||||
|
Color.Material.Background.light
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
// Content
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
// Header
|
||||||
|
SharedNavigationBar(
|
||||||
|
leftButton: .init(
|
||||||
|
image: Image(systemName: "chevron.left"),
|
||||||
|
action: {
|
||||||
|
router.dismissScreen()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
centerText: .init(text: centerText())
|
||||||
|
)
|
||||||
|
|
||||||
|
// Settings list
|
||||||
|
// ScrollView {
|
||||||
|
// LazyVStack(spacing: 0) {
|
||||||
|
// SharedListRow(
|
||||||
|
// iconType: .none,
|
||||||
|
// text: L10n.Conversation.Settings.enableOmemo,
|
||||||
|
// controlType: .switcher(isOn: Binding(
|
||||||
|
// get: { settingsStore.chat?.encrypted ?? false },
|
||||||
|
// set: { new in
|
||||||
|
// settingsStore.setSecured(new)
|
||||||
|
// }
|
||||||
|
// ))
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func centerText() -> String {
|
||||||
|
// TODO: make center text depend on conversation type in future (chat, group chat, channel, etc.)
|
||||||
|
L10n.Conversation.Settings.Title.chat
|
||||||
|
}
|
||||||
|
}
|
104
Monal/another.im/Views/Conversation/ConversationTextInput.swift
Normal file
104
Monal/another.im/Views/Conversation/ConversationTextInput.swift
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
struct ConversationTextInput: View {
|
||||||
|
@Environment(\.router) var router
|
||||||
|
// @EnvironmentObject var messages: MessagesStore
|
||||||
|
// @EnvironmentObject var attachments: AttachmentsStore
|
||||||
|
|
||||||
|
@State private var messageStr = ""
|
||||||
|
@FocusState private var isFocused: Bool
|
||||||
|
@Binding var autoScroll: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(.Material.Shape.separator)
|
||||||
|
.frame(height: 0.5)
|
||||||
|
.padding(.bottom, 8)
|
||||||
|
// if !messages.replyText.isEmpty {
|
||||||
|
// VStack(spacing: 0) {
|
||||||
|
// HStack(alignment: .top) {
|
||||||
|
// Text(messages.replyText)
|
||||||
|
// .font(.body3)
|
||||||
|
// .foregroundColor(Color.Material.Text.main)
|
||||||
|
// .multilineTextAlignment(.leading)
|
||||||
|
// .lineLimit(3)
|
||||||
|
// .padding(8)
|
||||||
|
// Spacer()
|
||||||
|
// Image(systemName: "xmark")
|
||||||
|
// .font(.title2)
|
||||||
|
// .foregroundColor(.Material.Elements.active)
|
||||||
|
// .padding(.leading, 8)
|
||||||
|
// .tappablePadding(.symmetric(8)) {
|
||||||
|
// messages.replyText = ""
|
||||||
|
// }
|
||||||
|
// .padding(8)
|
||||||
|
// }
|
||||||
|
// .frame(maxWidth: .infinity)
|
||||||
|
// .background(RoundedRectangle(cornerRadius: 4)
|
||||||
|
// .foregroundColor(.Material.Background.light)
|
||||||
|
// .shadow(radius: 0.5)
|
||||||
|
// )
|
||||||
|
// .padding(.bottom, 8)
|
||||||
|
// .padding(.horizontal, 8)
|
||||||
|
// }
|
||||||
|
// .padding(.horizontal, 8)
|
||||||
|
// }
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "paperclip")
|
||||||
|
.font(.title2)
|
||||||
|
.foregroundColor(.Material.Elements.active)
|
||||||
|
.padding(.leading, 8)
|
||||||
|
.tappablePadding(.symmetric(8)) {
|
||||||
|
router.showScreen(.fullScreenCover) { _ in
|
||||||
|
AttachmentPickerScreen()
|
||||||
|
// .environmentObject(messages)
|
||||||
|
// .environmentObject(attachments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextField("", text: $messageStr, prompt: Text(L10n.Chat.textfieldPrompt).foregroundColor(.Material.Shape.separator), axis: .vertical)
|
||||||
|
.font(.body1)
|
||||||
|
.foregroundColor(Color.Material.Text.main)
|
||||||
|
.accentColor(.Material.Shape.black)
|
||||||
|
.focused($isFocused)
|
||||||
|
.padding(.horizontal, 8)
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
.background(Color.Material.Shape.white)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
let img = messageStr.isEmpty ? "paperplane" : "paperplane.fill"
|
||||||
|
Image(systemName: img)
|
||||||
|
.font(.title2)
|
||||||
|
.foregroundColor(messageStr.isEmpty ? .Material.Elements.inactive : .Material.Elements.active)
|
||||||
|
.padding(.trailing, 8)
|
||||||
|
.tappablePadding(.symmetric(8)) {
|
||||||
|
if !messageStr.isEmpty {
|
||||||
|
// messages.sendMessage(composedMessage)
|
||||||
|
// messageStr = ""
|
||||||
|
// autoScroll = true
|
||||||
|
// if !messages.replyText.isEmpty {
|
||||||
|
// messages.replyText = ""
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom, 8)
|
||||||
|
.background(Color.Material.Background.dark)
|
||||||
|
// .onChange(of: messages.replyText) { new in
|
||||||
|
// if !new.isEmpty {
|
||||||
|
// isFocused = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private var composedMessage: String {
|
||||||
|
var result = ""
|
||||||
|
// if !messages.replyText.isEmpty {
|
||||||
|
// result += messages.replyText.makeReply + "\n\n"
|
||||||
|
// }
|
||||||
|
// result += messageStr
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,6 +54,11 @@ extension MonalXmppWrapper {
|
||||||
throw AimErrors.contactRemoveError
|
throw AimErrors.contactRemoveError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func chat(with: Contact) -> ChatModel {
|
||||||
|
let chatModel = ChatModel(contact: with)
|
||||||
|
return chatModel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Try login from Login screen
|
// MARK: - Try login from Login screen
|
||||||
|
@ -160,3 +165,12 @@ private extension MonalXmppWrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Chat object
|
||||||
|
final class ChatModel: ObservableObject {
|
||||||
|
let contact: Contact
|
||||||
|
|
||||||
|
init(contact: Contact) {
|
||||||
|
self.contact = contact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue