From 5e8244361de59ea63006fb34052a7e7bfea2fa4f Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Tue, 20 Sep 2022 17:10:38 +0200 Subject: [PATCH] [WIP] Proof of concept for PreviewProvider --- .../ManagedEntityViewModel.swift | 8 +- .../Previews/Previews.swift | 92 +++++++++ .../Previews/SharedPreviewData.swift | 186 ++++++++++++++++++ .../Previews/UIKitPreview.swift | 45 +++++ chat-components-ios.xcodeproj/project.pbxproj | 20 ++ 5 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 Sources/PubNubChatComponents/Previews/Previews.swift create mode 100644 Sources/PubNubChatComponents/Previews/SharedPreviewData.swift create mode 100644 Sources/PubNubChatComponents/Previews/UIKitPreview.swift diff --git a/Sources/PubNubChatComponents/ManagedEntityViewModel.swift b/Sources/PubNubChatComponents/ManagedEntityViewModel.swift index 01f4356..cce398e 100644 --- a/Sources/PubNubChatComponents/ManagedEntityViewModel.swift +++ b/Sources/PubNubChatComponents/ManagedEntityViewModel.swift @@ -98,7 +98,13 @@ extension PubNubManagedChannel: ManagedChannelViewModel { .eraseToAnyPublisher() } - public var messagesPublisher: AnyPublisher, Never> { + // This line shouldn't be changed. + // I got the compiler error while adopting `ManagedMessageViewModel` for my custom struct defined in SharedPreviewData.swift + // That's why I haven't solved it yet and created a temporary workaround. The error message says: + // + // "Reference to invalid associated type MessageViewModel of type PubNubManagedChannel" + // + public var messagesPublisher: AnyPublisher, Never> { return publisher(for: \.messages).eraseToAnyPublisher() } diff --git a/Sources/PubNubChatComponents/Previews/Previews.swift b/Sources/PubNubChatComponents/Previews/Previews.swift new file mode 100644 index 0000000..9ac2cda --- /dev/null +++ b/Sources/PubNubChatComponents/Previews/Previews.swift @@ -0,0 +1,92 @@ +// +// File 2.swift +// +// +// Created by Jakub Guz on 9/14/22. +// + +import PubNub +import PubNubChat +import CoreData +import Combine +import UIKit +import SwiftUI +import ChatLayout + +#if canImport(SwiftUI) && DEBUG + +struct ExamplePreview: PreviewProvider { + + static var previews: some View { + + Group() { + + // Light Mode + UIViewPreview() { + let view = MessageListItemCell() + view.configure(MyViewModel(), theme: .incomingGroupChat) + return view + } + .previewLayout(.fixed(width: 414, height: 140)) + .previewDisplayName("Light Mode") + .preferredColorScheme(.light) + + // Dark Mode + UIViewPreview() { + let view = MessageListItemCell() + view.configure(MyViewModel(), theme: .incomingGroupChat) + return view + } + .previewLayout(.fixed(width: 414, height: 140)) + .previewDisplayName("Dark Mode") + .preferredColorScheme(.dark) + + // RTL + UIViewPreview() { + let view = MessageListItemCell() + view.configure(MyViewModel(), theme: .incomingGroupChat) + return view + } + .environment(\.layoutDirection, .rightToLeft) + .previewLayout(.fixed(width: 414, height: 140)) + .previewDisplayName("RTL") + + // Accessibility + UIViewPreview() { + + let view = MessageListItemCell() + view.configure(MyViewModel(), theme: .incomingGroupChat) + return view + } + .environment(\.sizeCategory, .accessibilityExtraLarge) + .previewLayout(.fixed(width: 414, height: 140)) + .previewDisplayName("Accessibility") + + UIViewControllerPreview() { + let vm = testChatProvider.senderMembershipsChanneListComponentViewModel() + let vc = vm.configuredComponentView() + let navv = UINavigationController(rootViewController: vc) + + return navv + } + .previewDisplayName("VC") + + if #available(iOS 15.0, *) { + UIViewControllerPreview() { + let vm = testChatProvider.senderMembershipsChanneListComponentViewModel() + let vc = vm.configuredComponentView() + let navv = UINavigationController(rootViewController: vc) + + return navv + + } + .previewInterfaceOrientation(.landscapeRight) + .previewDisplayName("VC2") + } else { + // Fallback on earlier versions + } + } + } +} + +#endif diff --git a/Sources/PubNubChatComponents/Previews/SharedPreviewData.swift b/Sources/PubNubChatComponents/Previews/SharedPreviewData.swift new file mode 100644 index 0000000..71f8b68 --- /dev/null +++ b/Sources/PubNubChatComponents/Previews/SharedPreviewData.swift @@ -0,0 +1,186 @@ +// +// File 2.swift +// +// +// Created by Jakub Guz on 9/19/22. +// + +#if canImport(SwiftUI) && DEBUG + +import Foundation +import PubNub +import PubNubChat +import CoreData +import Combine + +// The idea could be creating a shared class (or something similar) that holds shared data. +// Then it's up to you what data you would like to use in your previews. Please don't treat existing implementation very serious, it's rather a POC + +// MARK: ChatProvider with in-memory storage + +public let testChatProvider: PubNubChatProvider = { + + let chatProvider = PubNubChatProvider( + pubnubProvider: PubNub(configuration: PubNubConfiguration(publishKey: "...", subscribeKey: "...", userId: "JG")), + coreDataProvider: try! CoreDataProvider(location: .memory, flushDataOnLoad: false), + cacheProvider: UserDefaults.standard + ) + + let channel = PubNubChatChannel( + id: "CHANNEL1", + name: "Channel 1", + type: "direct", + status: "status", + details: "Channel details", + avatarURL: URL(string: "https://picsum/photos/200/300"), + updated: nil, + eTag: nil, + custom: VoidCustomData() + ) + + let user1 = PubNubChatUser( + id: "JG", + name: "JG" + ) + + let message = PubNubChatMessage( + id: "12345", + text: "Hello, world!!!", + pubnubUserId: "JG", + pubnubChannelId: "CHANNEL1" + ) + + let membership = PubNubChatMember( + channelId: "CHANNEL1", + userId: "JG" + ) + + chatProvider.dataProvider.load(users: [user1]) + chatProvider.dataProvider.load(members: [membership]) + chatProvider.dataProvider.load(channels: [channel]) + chatProvider.dataProvider.load(messages: [message], processMessageActions: false) + + return chatProvider + +}() + +// MARK: ManagedMessageViewModel conformance + +class MyViewModel: ManagedMessageViewModel { + + public typealias Entity = PubNubManagedMessage + public typealias ChannelViewModel = PubNubManagedChannel + public typealias UserViewModel = PubNubManagedUser + public typealias MessageActionModel = PubNubManagedMessageAction + + public var pubnubId: Timetoken { return 0 } + public var managedObjectId: NSManagedObjectID { return NSManagedObjectID() } + + public func decodedContent(from: T.Type) throws -> T { + return try JSONDecoder().decode(T.self, from: Data()) + } + + public var messageContentTypePublisher: AnyPublisher { + Just("TEXT").eraseToAnyPublisher() + } + + public var messageContentPublisher: AnyPublisher { + Just(Data()).eraseToAnyPublisher() + } + + public var messageTextPublisher: AnyPublisher { + Just( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore." + ).eraseToAnyPublisher() + } + + public var messageCustomPublisher: AnyPublisher { + Just(Data()).eraseToAnyPublisher() + } + + public var messageDateCreatedPublisher: AnyPublisher { + Just(Date()).eraseToAnyPublisher() + } + + public var messageActionsPublisher: AnyPublisher, Never> { + Just(Set()).eraseToAnyPublisher() + } + + public var messageActions: Set { + Set() + } + + public var userViewModel: PubNubManagedUser { + + let user = PubNubManagedUser(context: testChatProvider.coreDataContainer.viewContext) + user.avatarURL = URL(string: "https://picsum.photos/100/300") + user.id = "JG" + user.name = "Jakub Jakub Jakub" + + return user + } + + public var text: String { + String("Text Text Text !!!") + } + + public var channelViewModel: PubNubManagedChannel { + PubNubManagedChannel() + } + + public var messageActionViewModels: Set { + Set() + } +} + +// MARK: ManagedChannelViewModel conformance + +class MyChannelViewModel: ManagedChannelViewModel { + + typealias Entity = PubNubManagedChannel + typealias MessageViewModel = PubNubManagedMessage + + public var pubnubId: String { return String() } + public var managedObjectId: NSManagedObjectID { return NSManagedObjectID() } + + public var channelNamePublisher: AnyPublisher { + Just("Channel name").eraseToAnyPublisher() + } + + public var channelDetailsPublisher: AnyPublisher { + Just("Channel details").eraseToAnyPublisher() + } + + public var channelAvatarUrlPublisher: AnyPublisher { + Just(URL(string: "https://picsum.photos/300/400")!).eraseToAnyPublisher() + } + + public var channelTypePublisher: AnyPublisher { + Just("Default").eraseToAnyPublisher() + } + public var channelCustomPublisher: AnyPublisher { + Just(Data()).eraseToAnyPublisher() + } + + public var membershipPublisher: AnyPublisher, Never> { + Just(Set()).eraseToAnyPublisher() + } + + public var memberCountPublisher: AnyPublisher { + Just(5).eraseToAnyPublisher() + } + + public var presentMemberCountPublisher: AnyPublisher { + Just(5).eraseToAnyPublisher() + } + + public var messagesPublisher: AnyPublisher, Never> { + Just(Set()).eraseToAnyPublisher() + } + + public var oldestMessagePublisher: AnyPublisher { + Just(nil).eraseToAnyPublisher() + } +} + +#endif diff --git a/Sources/PubNubChatComponents/Previews/UIKitPreview.swift b/Sources/PubNubChatComponents/Previews/UIKitPreview.swift new file mode 100644 index 0000000..a223fb5 --- /dev/null +++ b/Sources/PubNubChatComponents/Previews/UIKitPreview.swift @@ -0,0 +1,45 @@ +// +// File.swift +// +// +// Created by Jakub Guz on 9/14/22. +// + +#if canImport(SwiftUI) && DEBUG + +import SwiftUI + +struct UIViewPreview: UIViewRepresentable { + let view: View + + init(_ builder: @escaping () -> View) { + view = builder() + } + + func makeUIView(context: Context) -> UIView { + return view + } + + func updateUIView(_ view: UIView, context: Context) { + view.setContentHuggingPriority(.defaultHigh, for: .vertical) + view.setContentHuggingPriority(.defaultHigh, for: .horizontal) + } +} + +struct UIViewControllerPreview: UIViewControllerRepresentable { + let viewController: ViewController + + init(_ builder: @escaping () -> ViewController) { + viewController = builder() + } + + func makeUIViewController(context: Context) -> ViewController { + viewController + } + + func updateUIViewController(_ uiViewController: ViewController, context: Context) { + + } +} + +#endif diff --git a/chat-components-ios.xcodeproj/project.pbxproj b/chat-components-ios.xcodeproj/project.pbxproj index adabe43..286e374 100644 --- a/chat-components-ios.xcodeproj/project.pbxproj +++ b/chat-components-ios.xcodeproj/project.pbxproj @@ -108,6 +108,9 @@ 35F46DFE26C1D90B0024FB67 /* InputBarAccessoryView in Frameworks */ = {isa = PBXBuildFile; productRef = 35F46DFD26C1D90B0024FB67 /* InputBarAccessoryView */; }; 35FD757E269F8714007D7435 /* PubNubManagedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FD757D269F8714007D7435 /* PubNubManagedMessage.swift */; }; 35FD758126A0DD10007D7435 /* ChatMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FD758026A0DD10007D7435 /* ChatMessage.swift */; }; + 3D9F174928DA0B2D00D9BF17 /* PreviewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9F174628DA0B2D00D9BF17 /* PreviewData.swift */; }; + 3D9F174A28DA0B2D00D9BF17 /* UIKitPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9F174728DA0B2D00D9BF17 /* UIKitPreview.swift */; }; + 3D9F174B28DA0B2D00D9BF17 /* UIKitPreviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9F174828DA0B2D00D9BF17 /* UIKitPreviews.swift */; }; 84D5E8DC28B63AD9000DDE7E /* AddMessageReactionComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D5E8DB28B63AD9000DDE7E /* AddMessageReactionComponent.swift */; }; 84D5E8DE28B63CDA000DDE7E /* AddMessageReactionAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D5E8DD28B63CDA000DDE7E /* AddMessageReactionAnimation.swift */; }; /* End PBXBuildFile section */ @@ -241,6 +244,9 @@ 35F436D72707B40300033720 /* MessageInputComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MessageInputComponent.swift; path = "../Message Input/MessageInputComponent.swift"; sourceTree = ""; }; 35FD757D269F8714007D7435 /* PubNubManagedMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubNubManagedMessage.swift; sourceTree = ""; }; 35FD758026A0DD10007D7435 /* ChatMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessage.swift; sourceTree = ""; }; + 3D9F174628DA0B2D00D9BF17 /* PreviewData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewData.swift; sourceTree = ""; }; + 3D9F174728DA0B2D00D9BF17 /* UIKitPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitPreview.swift; sourceTree = ""; }; + 3D9F174828DA0B2D00D9BF17 /* UIKitPreviews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitPreviews.swift; sourceTree = ""; }; 84D5E8DB28B63AD9000DDE7E /* AddMessageReactionComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddMessageReactionComponent.swift; sourceTree = ""; }; 84D5E8DD28B63CDA000DDE7E /* AddMessageReactionAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddMessageReactionAnimation.swift; sourceTree = ""; }; 84D5E91728B772F9000DDE7E /* PubNubChatModel-V3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "PubNubChatModel-V3.xcdatamodel"; sourceTree = ""; }; @@ -369,6 +375,7 @@ 35F436D9270A3A1800033720 /* Theming */, 3583AE5726FBD3CA006D74A3 /* Helpers */, 359EBE8B265C3B6000D9F363 /* Extensions */, + 3D9F174528DA0B2D00D9BF17 /* Previews */, 350AC71E2643496300C3D323 /* PubNubChatComponents.h */, 350AC71F2643496300C3D323 /* Info.plist */, ); @@ -695,6 +702,16 @@ path = APIs; sourceTree = ""; }; + 3D9F174528DA0B2D00D9BF17 /* Previews */ = { + isa = PBXGroup; + children = ( + 3D9F174628DA0B2D00D9BF17 /* PreviewData.swift */, + 3D9F174728DA0B2D00D9BF17 /* UIKitPreview.swift */, + 3D9F174828DA0B2D00D9BF17 /* UIKitPreviews.swift */, + ); + path = Previews; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -970,6 +987,7 @@ 352A5D22270B953300B521A9 /* ChannelListComponentTheme.swift in Sources */, 3501A587265D8BDF00063403 /* AppearanceTemplate.swift in Sources */, 35DB0C512875E0B6001E1F76 /* MessageReactionListView.swift in Sources */, + 3D9F174B28DA0B2D00D9BF17 /* UIKitPreviews.swift in Sources */, 350BFB0F27290DC400F0F95B /* ChannelMemberComponentCell.swift in Sources */, 35F436CA2702236600033720 /* ThemeProvider.swift in Sources */, 35F436D02704BB0000033720 /* Combine+PubNub.swift in Sources */, @@ -981,6 +999,7 @@ 35B3B5BA2656C21C00473120 /* ImageComponentView.swift in Sources */, 35F436C427017B3100033720 /* ReusableLabelViewComponent.swift in Sources */, 35F436C02700FA9C00033720 /* MemberListComponentViewModel.swift in Sources */, + 3D9F174A28DA0B2D00D9BF17 /* UIKitPreview.swift in Sources */, 35F436BC26FEB0CB00033720 /* ManagedEntityListViewModel.swift in Sources */, 355AAD0126C719C3004E4231 /* TextComponentView.swift in Sources */, 357F2243272A388000A6FBF7 /* MessageListViewController.swift in Sources */, @@ -991,6 +1010,7 @@ 84D5E8DC28B63AD9000DDE7E /* AddMessageReactionComponent.swift in Sources */, 352FA63726F7F5C20074D75C /* MessageListComponentViewModel.swift in Sources */, 350BFB0C27290C3D00F0F95B /* LinkMetadataService.swift in Sources */, + 3D9F174928DA0B2D00D9BF17 /* PreviewData.swift in Sources */, 355526D226D456C2009698E3 /* ContainerView.swift in Sources */, 35F436D82707B40300033720 /* MessageInputComponent.swift in Sources */, 350E046626F0041100DD28F1 /* String+Components.swift in Sources */,