diff --git a/PopupKit/Sources/PopupKit/Implementation/Models/NotificationModel.swift b/PopupKit/Sources/PopupKit/Implementation/Models/NotificationModel.swift index cab717fba..473fc8c6c 100644 --- a/PopupKit/Sources/PopupKit/Implementation/Models/NotificationModel.swift +++ b/PopupKit/Sources/PopupKit/Implementation/Models/NotificationModel.swift @@ -9,8 +9,22 @@ import UIKit import CommonKit struct NotificationModel: Equatable, Hashable { + static func == (lhs: NotificationModel, rhs: NotificationModel) -> Bool { + return lhs.icon == rhs.icon + && lhs.title == rhs.title + && lhs.description == rhs.description + } + + func hash(into hasher: inout Hasher) { + hasher.combine(icon) + hasher.combine(title) + hasher.combine(description) + hasher.combine(tapHandler) + } + let icon: UIImage? let title: String? let description: String? let tapHandler: IDWrapper<() -> Void>? + let cancelAutoDismiss: IDWrapper<() -> Void>? } diff --git a/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift b/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift index 4defc4893..a405c068f 100644 --- a/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift +++ b/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift @@ -10,8 +10,13 @@ import CommonKit struct NotificationView: View { @State private var dragTranslation: CGFloat = .zero + @State private var horizontalDragTranslation: CGFloat = .zero @State private var minTranslationForDismiss: CGFloat = .infinity + @State private var minTranslationXForDismiss: CGFloat = .infinity + @State private var isTextLimited: Bool = true + @Binding var dismissEdge: Edge + var onDismissEdgeChanged: ((Edge) -> Void)? let model: NotificationModel let safeAreaInsets: EdgeInsets let dismissAction: () -> Void @@ -27,9 +32,11 @@ struct NotificationView: View { .padding([.leading, .trailing], 15) .padding([.top, .bottom], 10) .background(GeometryReader(content: processGeometry)) + .expanded(axes: .horizontal) + .offset(y: dragTranslation < .zero ? dragTranslation : .zero) + .offset(x: horizontalDragTranslation < .zero ? horizontalDragTranslation : .zero) .gesture(dragGesture) .onTapGesture(perform: onTap) - .background(Color(.adamant.swipeBlockColor)) .cornerRadius(10) .padding(.horizontal, 15) .padding(.top, safeAreaInsets.top) @@ -48,7 +55,7 @@ private extension NotificationView { } var textStack: some View { - VStack(alignment: .leading, spacing: .zero) { + VStack(alignment: .leading, spacing: 3) { if let title = model.title { Text(title) .font(.system(size: 15, weight: .bold)) @@ -56,20 +63,38 @@ private extension NotificationView { if let description = model.description { Text(description) .font(.system(size: 13)) - .lineLimit(3) + .lineLimit(isTextLimited ? 3 : nil) + } } } var dragGesture: some Gesture { DragGesture() - .onChanged { dragTranslation = $0.translation.height } + .onChanged { + dragTranslation = $0.translation.height + horizontalDragTranslation = $0.translation.width + } .onEnded { - print(-$0.translation.height, minTranslationForDismiss) if $0.velocity.height < -100 || -$0.translation.height > minTranslationForDismiss { - dismissAction() + onDismissEdgeChanged?(.top) + Task { + dismissAction() + } + } else if $0.velocity.width < -100 || $0.translation.width > minTranslationXForDismiss { + onDismissEdgeChanged?(.leading) + Task { + dismissAction() + } + } else if $0.velocity.height > -100 || -$0.translation.height < minTranslationForDismiss { + horizontalDragTranslation = .zero + isTextLimited = false + model.cancelAutoDismiss?.value() } else { - withAnimation { dragTranslation = .zero } + withAnimation { + dragTranslation = .zero + horizontalDragTranslation = .zero + } } } } @@ -77,9 +102,11 @@ private extension NotificationView { func processGeometry(_ geometry: GeometryProxy) -> some View { DispatchQueue.main.async { minTranslationForDismiss = geometry.size.height / 2 + minTranslationXForDismiss = geometry.size.width / 2 } - return Color.clear + return Color.init(uiColor: .adamant.swipeBlockColor) + .cornerRadius(10) } func onTap() { diff --git a/PopupKit/Sources/PopupKit/Implementation/Views/PopupCoordinatorView.swift b/PopupKit/Sources/PopupKit/Implementation/Views/PopupCoordinatorView.swift index bced879de..be054b484 100644 --- a/PopupKit/Sources/PopupKit/Implementation/Views/PopupCoordinatorView.swift +++ b/PopupKit/Sources/PopupKit/Implementation/Views/PopupCoordinatorView.swift @@ -10,6 +10,7 @@ import CommonKit struct PopupCoordinatorView: View { @ObservedObject var model: PopupCoordinatorModel + @State var dismissEdge: Edge = .top var body: some View { GeometryReader { geomerty in @@ -34,14 +35,19 @@ private extension PopupCoordinatorView { VStack { if let notificationModel = model.notification { NotificationView( + dismissEdge: $dismissEdge, + onDismissEdgeChanged: { newEdge in + dismissEdge = newEdge + }, model: notificationModel, safeAreaInsets: safeAreaInsets, dismissAction: { [weak model] in model?.notification = nil + dismissEdge = .top } ) .id(model.notification?.hashValue) - .transition(.move(edge: .top)) + .transition(.move(edge: dismissEdge)) } Spacer() } diff --git a/PopupKit/Sources/PopupKit/PopupManager.swift b/PopupKit/Sources/PopupKit/PopupManager.swift index a237dbe5a..341a8ecb8 100644 --- a/PopupKit/Sources/PopupKit/PopupManager.swift +++ b/PopupKit/Sources/PopupKit/PopupManager.swift @@ -83,7 +83,10 @@ public extension PopupManager { icon: icon, title: title, description: description, - tapHandler: tapHandler.map { .init(id: .empty, value: $0) } + tapHandler: tapHandler.map { .init(id: .empty, value: $0) }, + cancelAutoDismiss: .init(id: .empty, value: { [weak self] in + self?.autoDismissManager.notificationDismissSubscription?.cancel() + }) ) if autoDismiss {