Sergey 2 years ago
parent
commit
1a57c2f89b

+ 34 - 4
Chat.xcodeproj/project.pbxproj

@@ -13,6 +13,14 @@
 		2F60458D288D787F008F005E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F60457B288D787E008F005E /* ContentView.swift */; };
 		2F60458E288D787F008F005E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2F60457C288D787F008F005E /* Assets.xcassets */; };
 		2F60458F288D787F008F005E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2F60457C288D787F008F005E /* Assets.xcassets */; };
+		2F604599288D7E00008F005E /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F604598288D7E00008F005E /* ChatView.swift */; };
+		2F60459A288D7E00008F005E /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F604598288D7E00008F005E /* ChatView.swift */; };
+		2F60459C288D8000008F005E /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F60459B288D8000008F005E /* MessageView.swift */; };
+		2F60459D288D8000008F005E /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F60459B288D8000008F005E /* MessageView.swift */; };
+		2F60459F288D869B008F005E /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F60459E288D869B008F005E /* ChatListView.swift */; };
+		2F6045A0288D869C008F005E /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F60459E288D869B008F005E /* ChatListView.swift */; };
+		2F6045A5288E19ED008F005E /* KeyboardReadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F6045A4288E19ED008F005E /* KeyboardReadable.swift */; };
+		2F6045A6288E19ED008F005E /* KeyboardReadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F6045A4288E19ED008F005E /* KeyboardReadable.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
@@ -22,6 +30,10 @@
 		2F604581288D787F008F005E /* Chat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Chat.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		2F604587288D787F008F005E /* Chat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Chat.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		2F604589288D787F008F005E /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = "<group>"; };
+		2F604598288D7E00008F005E /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
+		2F60459B288D8000008F005E /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = "<group>"; };
+		2F60459E288D869B008F005E /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = "<group>"; };
+		2F6045A4288E19ED008F005E /* KeyboardReadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardReadable.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -56,6 +68,10 @@
 			children = (
 				2F60457A288D787E008F005E /* ChatApp.swift */,
 				2F60457B288D787E008F005E /* ContentView.swift */,
+				2F60459E288D869B008F005E /* ChatListView.swift */,
+				2F604598288D7E00008F005E /* ChatView.swift */,
+				2F60459B288D8000008F005E /* MessageView.swift */,
+				2F6045A4288E19ED008F005E /* KeyboardReadable.swift */,
 				2F60457C288D787F008F005E /* Assets.xcassets */,
 			);
 			path = Shared;
@@ -176,7 +192,11 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				2F60459C288D8000008F005E /* MessageView.swift in Sources */,
+				2F60459F288D869B008F005E /* ChatListView.swift in Sources */,
+				2F6045A5288E19ED008F005E /* KeyboardReadable.swift in Sources */,
 				2F60458C288D787F008F005E /* ContentView.swift in Sources */,
+				2F604599288D7E00008F005E /* ChatView.swift in Sources */,
 				2F60458A288D787F008F005E /* ChatApp.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -185,7 +205,11 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				2F60459D288D8000008F005E /* MessageView.swift in Sources */,
+				2F6045A0288D869C008F005E /* ChatListView.swift in Sources */,
+				2F6045A6288E19ED008F005E /* KeyboardReadable.swift in Sources */,
 				2F60458D288D787F008F005E /* ContentView.swift in Sources */,
+				2F60459A288D7E00008F005E /* ChatView.swift in Sources */,
 				2F60458B288D787F008F005E /* ChatApp.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -243,6 +267,8 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+				MACOSX_DEPLOYMENT_TARGET = 12.0;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				ONLY_ACTIVE_ARCH = YES;
@@ -295,6 +321,8 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+				MACOSX_DEPLOYMENT_TARGET = 12.0;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				MTL_FAST_MATH = YES;
 				SWIFT_COMPILATION_MODE = wholemodule;
@@ -315,9 +343,10 @@
 				INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
 				INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
 				INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+				INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeRight UIInterfaceOrientationLandscapeLeft";
 				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
 				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
-				IPHONEOS_DEPLOYMENT_TARGET = 15.5;
+				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
@@ -345,9 +374,10 @@
 				INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
 				INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
 				INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+				INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeRight UIInterfaceOrientationLandscapeLeft";
 				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
 				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
-				IPHONEOS_DEPLOYMENT_TARGET = 15.5;
+				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
@@ -381,7 +411,7 @@
 					"$(inherited)",
 					"@executable_path/../Frameworks",
 				);
-				MACOSX_DEPLOYMENT_TARGET = 12.3;
+				MACOSX_DEPLOYMENT_TARGET = 12.0;
 				MARKETING_VERSION = 1.0;
 				PRODUCT_BUNDLE_IDENTIFIER = sertrsv.Chat;
 				PRODUCT_NAME = Chat;
@@ -409,7 +439,7 @@
 					"$(inherited)",
 					"@executable_path/../Frameworks",
 				);
-				MACOSX_DEPLOYMENT_TARGET = 12.3;
+				MACOSX_DEPLOYMENT_TARGET = 12.0;
 				MARKETING_VERSION = 1.0;
 				PRODUCT_BUNDLE_IDENTIFIER = sertrsv.Chat;
 				PRODUCT_NAME = Chat;

+ 18 - 0
Shared/ChatApp.swift

@@ -12,6 +12,24 @@ struct ChatApp: App {
     var body: some Scene {
         WindowGroup {
             ContentView()
+                .onAppear(perform: UIApplication.shared.addTapGestureRecognizer)
         }
     }
 }
+
+extension UIApplication {
+    func addTapGestureRecognizer() {
+        guard let window = windows.first else { return }
+        let tapGesture = UITapGestureRecognizer(target: window, action: #selector(UIView.endEditing))
+        tapGesture.requiresExclusiveTouchType = false
+        tapGesture.cancelsTouchesInView = false
+        tapGesture.delegate = self
+        window.addGestureRecognizer(tapGesture)
+    }
+}
+
+extension UIApplication: UIGestureRecognizerDelegate {
+    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
+        return false
+    }
+}

+ 78 - 0
Shared/ChatListView.swift

@@ -0,0 +1,78 @@
+//
+//  ChatListView.swift
+//  Chat
+//
+//  Created by Sergey Tarasov on 24.07.2022.
+//
+
+import SwiftUI
+
+struct ChatListView: View {
+    @Environment(\.colorScheme) var colorScheme
+    var body: some View {
+        List {
+            ForEach(1..<14) { index in
+                ZStack {
+                    HStack {
+                        Image(systemName: "person.crop.circle")
+                            .resizable()
+                            .scaledToFit()
+                            .foregroundColor(.secondary.opacity(0.5))
+                            .padding(.vertical, 8)
+                        VStack(alignment: .leading, spacing: 0) {
+                            HStack {
+                                Text("Андрей")
+                                Spacer()
+                                Text("23:04")
+                                    .font(.callout)
+                                    .foregroundColor(.secondary)
+                            }
+                            .padding(.vertical, 4)
+                            HStack {
+                                VStack {
+                                    Text("Сообщение \(index)")
+                                        .foregroundColor(.secondary)
+                                    Spacer()
+                                }
+                                Spacer()
+                                Text("\(index)")
+                                    .font(.callout)
+                                    .foregroundColor(colorScheme == .light ? .white : .black)
+                                    .padding(.horizontal, 6)
+                                    .background(
+                                        Capsule().foregroundColor(.secondary)
+                                    )
+                            }
+                            .padding(.vertical, 6)
+                        }
+                    }
+                    .foregroundColor(.primary)
+                    .frame(height: 60)
+                    NavigationLink {
+                        ChatView()
+                    } label: {
+                        EmptyView()
+                    }
+                        .opacity(0.0)
+                }
+            }
+        }
+        .listStyle(.plain)
+        .navigationTitle("Чаты")
+        .navigationBarTitleDisplayMode(.inline)
+    }
+}
+
+struct ChatListView_Previews: PreviewProvider {
+    static var previews: some View {
+        Group {
+            NavigationView {
+                ChatListView()
+            }
+            NavigationView {
+                ChatListView()
+            }
+            .preferredColorScheme(.dark)
+        }
+    }
+}

+ 96 - 0
Shared/ChatView.swift

@@ -0,0 +1,96 @@
+//
+//  ChatView.swift
+//  Chat
+//
+//  Created by Sergey Tarasov on 24.07.2022.
+//
+
+import SwiftUI
+
+extension CGFloat {
+    static let sidePadding: CGFloat = 14
+}
+
+struct ChatView: View, KeyboardReadable {
+    
+    @State private var isKeyboardVisible = false
+    @State var message: String = ""
+    @State var messages: [String] = ["Display a short text message, called an alert, that draws attention to something new in your app.", "Play a notification sound.", "Set a badge number on the app’s icon to let the user know there are new items.", "Provide actions the user can take without opening the app.", "Show a media attachment.", "Be silent, allowing the app to perform a task in the background.", "No", "Group notifications into threads.", "Yes", "Edit or remove delivered notifications.", "Run code to change your notification before displaying it.", "Display a custom, interactive UI for your notification.", "And probably more.", "Yes", "Edit or remove delivered notifications.", "Run code to change your notification before displaying it.", "Display a custom, interactive UI for your notification.", "And probably more."]
+
+    var body: some View {
+        VStack(alignment: .center, spacing: 0) {
+            ScrollView {
+                ScrollViewReader { value in
+                    VStack {
+                        withAnimation {
+                            ForEach(messages.indices, id: \.self) { i in
+                                HStack {
+                                    if messages[i] == messages[3] || messages[i] == messages[6] || messages[i] == messages[8] || i > 17{
+                                        Spacer()
+                                        MessageView(text: messages[i], time: "23:03", isSelf: true)
+                                    } else {
+                                        MessageView(text: messages[i], time: "23:03", isSelf: false)
+                                        Spacer()
+                                    }
+                                }
+                            }
+                            .onChange(of: messages) { _ in
+                                withAnimation {
+                                    value.scrollTo(messages.count - 1, anchor: .bottomTrailing)
+                                }
+                            }
+                            .onReceive(keyboardPublisher) { newIsKeyboardVisible in
+                                isKeyboardVisible = newIsKeyboardVisible
+                                withAnimation {
+                                    value.scrollTo(messages.count - 2, anchor: .topTrailing)
+                                }
+                            }
+                            .onAppear {
+                                value.scrollTo(messages.count - 1, anchor: .top)
+                            }
+                        }
+                    }
+                    .padding(.horizontal, .sidePadding)
+                    .padding(.vertical, 12)
+                    .background(Color.indigo.opacity(0.0))
+                }
+            }
+            Divider()
+            HStack {
+                TextField("Сообщение", text: $message)
+                    .textFieldStyle(.automatic)
+                    .padding(.vertical, 6)
+                    .padding(.horizontal, 12)
+                    .background(.background)
+                Button {
+                    messages.append(message)
+                    message = ""
+                } label: {
+                    Image(systemName: "arrow.up.circle.fill")
+                        .font(.title)
+                        .foregroundColor(message.isEmpty ? .gray : .accentColor)
+                }
+                .disabled(message.isEmpty)
+            }
+            .padding(.horizontal, .sidePadding)
+            .padding(.vertical, 6)
+            .background()
+        }
+        .navigationTitle("Андрей")
+        .navigationBarTitleDisplayMode(.inline)
+    }
+}
+
+struct ChatView_Previews: PreviewProvider {
+    static var previews: some View {
+        Group {
+            NavigationView {
+                ChatView()
+            }
+            NavigationView {
+                ChatView()
+                    .preferredColorScheme(.dark)
+            }
+        }
+    }
+}

+ 3 - 2
Shared/ContentView.swift

@@ -9,8 +9,9 @@ import SwiftUI
 
 struct ContentView: View {
     var body: some View {
-        Text("Hello, world!")
-            .padding()
+        NavigationView {
+            ChatListView()
+        }
     }
 }
 

+ 30 - 0
Shared/KeyboardReadable.swift

@@ -0,0 +1,30 @@
+//
+//  KeyboardReadable.swift
+//  Chat
+//
+//  Created by Sergey Tarasov on 25.07.2022.
+//
+
+import Combine
+import UIKit
+
+
+/// Publisher to read keyboard changes.
+protocol KeyboardReadable {
+    var keyboardPublisher: AnyPublisher<Bool, Never> { get }
+}
+
+extension KeyboardReadable {
+    var keyboardPublisher: AnyPublisher<Bool, Never> {
+        Publishers.Merge(
+            NotificationCenter.default
+                .publisher(for: UIResponder.keyboardWillShowNotification)
+                .map { _ in true },
+
+            NotificationCenter.default
+                .publisher(for: UIResponder.keyboardWillHideNotification)
+                .map { _ in false }
+        )
+        .eraseToAnyPublisher()
+    }
+}

+ 43 - 0
Shared/MessageView.swift

@@ -0,0 +1,43 @@
+//
+//  MessageView.swift
+//  Chat
+//
+//  Created by Sergey Tarasov on 24.07.2022.
+//
+
+import SwiftUI
+
+struct MessageView: View {
+    let text: String
+    let time: String
+    let isSelf: Bool
+    var body: some View {
+        HStack(alignment: .bottom) {
+            Text(text)
+                .foregroundColor(.primary)
+                .padding(.vertical, 6)
+                .padding(.horizontal, 12)
+            Text(time)
+                .font(.caption)
+                .foregroundColor(.secondary)
+                .padding(.vertical, 6)
+                .padding(.trailing, 12)
+                .padding(.leading, -12)
+        }
+        .background(
+            RoundedRectangle(cornerRadius: 17)
+                .foregroundColor(isSelf ? .mint.opacity(0.1) : .secondary.opacity(0.1))
+        )
+    }
+}
+
+struct MessageView_Previews: PreviewProvider {
+    static var previews: some View {
+        MessageView(text: "Малое пробное сообщение", time: "23:05", isSelf: true)
+            .padding()
+            .previewLayout(.sizeThatFits)
+        MessageView(text: "Пробное сообщение на несколько строк для проверки того, как выглядит сообщение и его фон", time: "23:08", isSelf: false)
+            .padding()
+            .previewLayout(.sizeThatFits)
+    }
+}