-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Proof of concept for PreviewProvider #24
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These three modifiers could be packaged together as a single modifier to simplify reuse when we add similar configurations to other previews. (Same for the following examples) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's not even a work in progress, but a quick POC instead which I wanted to share with you to make a decision. I can provide a more robust PR converted to |
||
|
||
// 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. UIViewPreview contains always the same view, it can be created once in function There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Additionally in that way it will be more self-describing and comments would not be necessary There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I'm sure there are better options to solve it. I just wanted to quickly check if it's ever possible to use previews in Chat Components (it's a spike) and what are possible limitations so don't treat the current implementation very serious :) |
||
} | ||
.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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct me if I am wrong, but current design supports inheritance so you can make new TestChatProvider class. Is it safe to share testChatProvider between all of previews? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be safe as long as provider remains immutable. It may not be possible though so we could separately create |
||
|
||
let chatProvider = PubNubChatProvider( | ||
pubnubProvider: PubNub(configuration: PubNubConfiguration(publishKey: "...", subscribeKey: "...", userId: "JG")), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know that it is not working but is SDK sending request to API with this keys? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you interact with your view controller in the In my opinion, previews should be isolated and give you quick answer whether your view (or view controller) looks as expected for various scenarios so the fastest possible way to achieve it should be providing them mock data. Anyway, we could also consider if it's possible to disable any traffic between Components and PubNub server for such previews. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thats true, preview should not send any requests and I am thinking if it is easy to mock provider, it would help here and in unit tests. |
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was there an issue with passing in our PubNubManaged entities directly instead of creating custom View Model implementations with dummy data? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was a quick try for this POC. I just checked and there are no issues with passing in our PubNubManaged entities. |
||
|
||
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<T: Decodable>(from: T.Type) throws -> T { | ||
return try JSONDecoder().decode(T.self, from: Data()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we need decision if we using return in single lines or not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have been historically including the return, but we can default to the standard when we add SwiftLint and/or SwiftFormat plugin support. |
||
} | ||
|
||
public var messageContentTypePublisher: AnyPublisher<String, Never> { | ||
Just("TEXT").eraseToAnyPublisher() | ||
} | ||
|
||
public var messageContentPublisher: AnyPublisher<Data, Never> { | ||
Just(Data()).eraseToAnyPublisher() | ||
} | ||
|
||
public var messageTextPublisher: AnyPublisher<String, Never> { | ||
Just( | ||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore." | ||
).eraseToAnyPublisher() | ||
} | ||
|
||
public var messageCustomPublisher: AnyPublisher<Data, Never> { | ||
Just(Data()).eraseToAnyPublisher() | ||
} | ||
|
||
public var messageDateCreatedPublisher: AnyPublisher<Date, Never> { | ||
Just(Date()).eraseToAnyPublisher() | ||
} | ||
|
||
public var messageActionsPublisher: AnyPublisher<Set<PubNubManagedMessageAction>, Never> { | ||
Just(Set<PubNubManagedMessageAction>()).eraseToAnyPublisher() | ||
} | ||
|
||
public var messageActions: Set<PubNubManagedMessageAction> { | ||
Set<PubNubManagedMessageAction>() | ||
} | ||
|
||
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<PubNubManagedMessageAction> { | ||
Set<PubNubManagedMessageAction>() | ||
} | ||
} | ||
|
||
// 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<String?, Never> { | ||
Just("Channel name").eraseToAnyPublisher() | ||
} | ||
|
||
public var channelDetailsPublisher: AnyPublisher<String?, Never> { | ||
Just("Channel details").eraseToAnyPublisher() | ||
} | ||
|
||
public var channelAvatarUrlPublisher: AnyPublisher<URL?, Never> { | ||
Just(URL(string: "https://picsum.photos/300/400")!).eraseToAnyPublisher() | ||
} | ||
|
||
public var channelTypePublisher: AnyPublisher<String, Never> { | ||
Just("Default").eraseToAnyPublisher() | ||
} | ||
public var channelCustomPublisher: AnyPublisher<Data, Never> { | ||
Just(Data()).eraseToAnyPublisher() | ||
} | ||
|
||
public var membershipPublisher: AnyPublisher<Set<PubNubManagedMember>, Never> { | ||
Just(Set<PubNubManagedMember>()).eraseToAnyPublisher() | ||
} | ||
|
||
public var memberCountPublisher: AnyPublisher<Int, Never> { | ||
Just(5).eraseToAnyPublisher() | ||
} | ||
|
||
public var presentMemberCountPublisher: AnyPublisher<Int, Never> { | ||
Just(5).eraseToAnyPublisher() | ||
} | ||
|
||
public var messagesPublisher: AnyPublisher<Set<PubNubManagedMessage>, Never> { | ||
Just(Set<PubNubManagedMessage>()).eraseToAnyPublisher() | ||
} | ||
|
||
public var oldestMessagePublisher: AnyPublisher<PubNubManagedMessage?, Never> { | ||
Just(nil).eraseToAnyPublisher() | ||
} | ||
} | ||
|
||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by Jakub Guz on 9/14/22. | ||
// | ||
|
||
#if canImport(SwiftUI) && DEBUG | ||
|
||
import SwiftUI | ||
|
||
struct UIViewPreview<View: UIView>: 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<ViewController: UIViewController>: UIViewControllerRepresentable { | ||
let viewController: ViewController | ||
|
||
init(_ builder: @escaping () -> ViewController) { | ||
viewController = builder() | ||
} | ||
|
||
func makeUIViewController(context: Context) -> ViewController { | ||
viewController | ||
} | ||
|
||
func updateUIViewController(_ uiViewController: ViewController, context: Context) { | ||
|
||
} | ||
} | ||
|
||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we still need DEBUG for PreviewProvider? It should be removed by compiler
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this at all, what situation would SwiftUI not be importable? Linux?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, we don't need it. You can easily import
SwiftUI
for all Apple's platforms so this check is redundant. Another idea could be storing all previews in a separate target like you recently suggested so perhaps we could avoid adding extra macros.