turbo-native-initializer 0.0.9 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +3 -1
  4. data/lib/turbo_native_initializer/generator.rb +1 -0
  5. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/base/NavDestination.kt.tt +17 -2
  6. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/features/web/WebFragment.kt.tt +38 -0
  7. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/features/web/WebModalFragment.kt.tt +2 -2
  8. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/main/MainActivity.kt.tt +7 -0
  9. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/main/MainSessionNavHostFragment.kt.tt +5 -5
  10. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/BridgeComponentFactories.kt.tt +9 -0
  11. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FlashMessageComponent.kt.tt +41 -0
  12. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FormComponent.kt.tt +87 -0
  13. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/NavButtonComponent.kt.tt +70 -0
  14. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/util/Extension.kt.tt +19 -0
  15. data/lib/turbo_native_initializer/templates/android_stack/base/app/build.gradle.kts.tt +9 -3
  16. data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/assets/json/configuration.json +1 -1
  17. data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/form_component_submit.xml +12 -0
  18. data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/nav_button_component.xml +14 -0
  19. data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/values/colors.xml +1 -1
  20. data/lib/turbo_native_initializer/templates/android_stack/base/build.gradle.kts +2 -1
  21. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/base/NavDestination.kt.tt +17 -2
  22. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/features/web/WebFragment.kt.tt +38 -0
  23. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/features/web/WebModalFragment.kt.tt +2 -2
  24. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/main/BaseSessionNavHostFragment.kt.tt +5 -5
  25. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/main/MainActivity.kt.tt +4 -0
  26. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/BridgeComponentFactories.kt.tt +9 -0
  27. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FlashMessageComponent.kt.tt +41 -0
  28. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FormComponent.kt.tt +87 -0
  29. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/NavButtonComponent.kt.tt +70 -0
  30. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/util/Extension.kt.tt +19 -0
  31. data/lib/turbo_native_initializer/templates/android_tabs/base/app/build.gradle.kts.tt +9 -3
  32. data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/assets/json/configuration.json +1 -1
  33. data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/form_component_submit.xml +12 -0
  34. data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/nav_button_component.xml +14 -0
  35. data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/values/colors.xml +1 -1
  36. data/lib/turbo_native_initializer/templates/android_tabs/base/build.gradle.kts +2 -1
  37. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Configuration/path-configuration.json +1 -1
  38. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/TurboNavigationController.swift +10 -1
  39. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/TurboWebViewController.swift +65 -0
  40. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/UIViewController+Toast.swift +59 -0
  41. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/WKWebViewConfiguration+App.swift +21 -0
  42. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Delegates/SceneDelegate.swift.tt +5 -5
  43. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Resources/Assets.xcassets/AccentColor.colorset/Contents.json +8 -8
  44. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Resources/Base.lproj/{Main.storyboard.tt → Main.storyboard} +1 -1
  45. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/BridgeComponent+App.swift +8 -0
  46. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/FlashMessageComponent.swift +47 -0
  47. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/FormComponent.swift +78 -0
  48. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/NavButtonComponent.swift +60 -0
  49. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject.xcodeproj/project.pbxproj.tt +55 -6
  50. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +9 -0
  51. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Configuration/path-configuration.json +1 -1
  52. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/TurboNavigationController.swift +10 -1
  53. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/TurboWebViewController.swift +65 -0
  54. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/UIViewController+Toast.swift +59 -0
  55. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/WKWebViewConfiguration+App.swift +21 -0
  56. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Delegates/SceneDelegate.swift.tt +5 -5
  57. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Resources/Assets.xcassets/AccentColor.colorset/Contents.json +8 -8
  58. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Resources/Base.lproj/Main.storyboard +2 -2
  59. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/BridgeComponent+App.swift +8 -0
  60. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/FlashMessageComponent.swift +47 -0
  61. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/FormComponent.swift +78 -0
  62. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/NavButtonComponent.swift +60 -0
  63. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject.xcodeproj/project.pbxproj.tt +55 -6
  64. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +9 -0
  65. data/lib/turbo_native_initializer/version.rb +1 -1
  66. metadata +32 -6
  67. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/ViewController.swift +0 -22
  68. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/ViewController.swift +0 -22
@@ -0,0 +1,47 @@
1
+ import Foundation
2
+ import Strada
3
+ import UIKit
4
+
5
+ final class FlashMessageComponent: BridgeComponent {
6
+ override class var name: String { "flash-message" }
7
+
8
+ override func onReceive(message: Message) {
9
+ guard let event = Event(rawValue: message.event) else {
10
+ return
11
+ }
12
+
13
+ if event == .connect {
14
+ handleConnectEvent(message: message)
15
+ }
16
+ }
17
+
18
+ @objc func performAction() {
19
+ reply(to: Event.connect.rawValue)
20
+ }
21
+
22
+ // MARK: Private
23
+
24
+ private var viewController: UIViewController? {
25
+ delegate.destination as? UIViewController
26
+ }
27
+
28
+ private func handleConnectEvent(message: Message) {
29
+ guard let data: MessageData = message.data() else { return }
30
+ guard let viewController else { return }
31
+ viewController.parent?.presentToast(data.title)
32
+ }
33
+ }
34
+
35
+ // MARK: Events
36
+
37
+ private extension FlashMessageComponent {
38
+ enum Event: String { case connect }
39
+ }
40
+
41
+ // MARK: Message data
42
+
43
+ private extension FlashMessageComponent {
44
+ struct MessageData: Decodable {
45
+ let title: String
46
+ }
47
+ }
@@ -0,0 +1,78 @@
1
+ import Foundation
2
+ import Strada
3
+ import UIKit
4
+
5
+ final class FormComponent: BridgeComponent {
6
+ override class var name: String { "form" }
7
+
8
+ override func onReceive(message: Message) {
9
+ guard let event = Event(rawValue: message.event) else {
10
+ return
11
+ }
12
+
13
+ switch event {
14
+ case .connect:
15
+ handleConnectEvent(message: message)
16
+ case .submitEnabled:
17
+ handleSubmitEnabled()
18
+ case .submitDisabled:
19
+ handleSubmitDisabled()
20
+ }
21
+ }
22
+
23
+ @objc func performAction() {
24
+ reply(to: Event.connect.rawValue)
25
+ }
26
+
27
+ // MARK: Private
28
+
29
+ private weak var submitBarButtonItem: UIBarButtonItem?
30
+
31
+ private var viewController: UIViewController? {
32
+ delegate.destination as? UIViewController
33
+ }
34
+
35
+ private func handleConnectEvent(message: Message) {
36
+ guard let data: MessageData = message.data() else { return }
37
+ configureBarButton(with: data.title)
38
+ }
39
+
40
+ private func handleSubmitEnabled() {
41
+ submitBarButtonItem?.isEnabled = true
42
+ }
43
+
44
+ private func handleSubmitDisabled() {
45
+ submitBarButtonItem?.isEnabled = false
46
+ }
47
+
48
+ private func configureBarButton(with title: String) {
49
+ guard let viewController else { return }
50
+
51
+ let button = UIButton(configuration: .filled())
52
+ button.setTitle(title, for: .normal)
53
+ button.addTarget(self, action: #selector(performAction), for: .touchUpInside)
54
+
55
+ let item = UIBarButtonItem(customView: button)
56
+
57
+ viewController.navigationItem.rightBarButtonItem = item
58
+ submitBarButtonItem = item
59
+ }
60
+ }
61
+
62
+ // MARK: Events
63
+
64
+ private extension FormComponent {
65
+ enum Event: String {
66
+ case connect
67
+ case submitEnabled
68
+ case submitDisabled
69
+ }
70
+ }
71
+
72
+ // MARK: Message data
73
+
74
+ private extension FormComponent {
75
+ struct MessageData: Decodable {
76
+ let title: String
77
+ }
78
+ }
@@ -0,0 +1,60 @@
1
+ import Foundation
2
+ import Strada
3
+ import UIKit
4
+
5
+ final class NavButtonComponent: BridgeComponent {
6
+ override class var name: String { "nav-button" }
7
+
8
+ override func onReceive(message: Message) {
9
+ guard let event = Event(rawValue: message.event) else {
10
+ return
11
+ }
12
+
13
+ if event == .connect {
14
+ handleConnectEvent(message: message)
15
+ }
16
+ }
17
+
18
+ @objc func performAction() {
19
+ reply(to: Event.connect.rawValue)
20
+ }
21
+
22
+ // MARK: Private
23
+
24
+ private weak var navBarButtonItem: UIBarButtonItem?
25
+
26
+ private var viewController: UIViewController? {
27
+ delegate.destination as? UIViewController
28
+ }
29
+
30
+ private func handleConnectEvent(message: Message) {
31
+ guard let data: MessageData = message.data() else { return }
32
+ configureBarButton(with: data.title)
33
+ }
34
+
35
+ private func configureBarButton(with title: String) {
36
+ guard let viewController else { return }
37
+
38
+ let item = UIBarButtonItem(title: title,
39
+ style: .plain,
40
+ target: self,
41
+ action: #selector(performAction))
42
+
43
+ viewController.navigationItem.rightBarButtonItem = item
44
+ navBarButtonItem = item
45
+ }
46
+ }
47
+
48
+ // MARK: Events
49
+
50
+ private extension NavButtonComponent {
51
+ enum Event: String { case connect }
52
+ }
53
+
54
+ // MARK: Message data
55
+
56
+ private extension NavButtonComponent {
57
+ struct MessageData: Decodable {
58
+ let title: String
59
+ }
60
+ }
@@ -8,9 +8,16 @@
8
8
 
9
9
  /* Begin PBXBuildFile section */
10
10
  5D0D861F2A9AA70500A525E9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5D0D86212A9AA70500A525E9 /* Main.storyboard */; };
11
+ 5D1F2EC12ABEB12300B2819A /* NavButtonComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1F2EC02ABEB12300B2819A /* NavButtonComponent.swift */; };
12
+ 5D281BAF2ABC0BBC001CE599 /* Strada in Frameworks */ = {isa = PBXBuildFile; productRef = 5D281BAE2ABC0BBC001CE599 /* Strada */; };
13
+ 5D281BB12ABC0C3D001CE599 /* WKWebViewConfiguration+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D281BB02ABC0C3D001CE599 /* WKWebViewConfiguration+App.swift */; };
14
+ 5D281BB42ABC0CB0001CE599 /* BridgeComponent+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D281BB32ABC0CB0001CE599 /* BridgeComponent+App.swift */; };
15
+ 5D281BB62ABC0CB9001CE599 /* FormComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D281BB52ABC0CB9001CE599 /* FormComponent.swift */; };
16
+ 5D91C5402AC538960046D872 /* FlashMessageComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D91C53F2AC538960046D872 /* FlashMessageComponent.swift */; };
17
+ 5DAF71B72AC3FB53002D04FE /* UIViewController+Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DAF71B62AC3FB53002D04FE /* UIViewController+Toast.swift */; };
11
18
  5DCC50D42A959DF900B529A0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCC50D32A959DF900B529A0 /* AppDelegate.swift */; };
12
19
  5DCC50D62A959DF900B529A0 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCC50D52A959DF900B529A0 /* SceneDelegate.swift */; };
13
- 5DCC50D82A959DF900B529A0 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCC50D72A959DF900B529A0 /* ViewController.swift */; };
20
+ 5DCC50D82A959DF900B529A0 /* TurboWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCC50D72A959DF900B529A0 /* TurboWebViewController.swift */; };
14
21
  5DCC50DD2A959DFA00B529A0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5DCC50DC2A959DFA00B529A0 /* Assets.xcassets */; };
15
22
  5DCC50E02A959DFA00B529A0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5DCC50DE2A959DFA00B529A0 /* LaunchScreen.storyboard */; };
16
23
  5DCC50E82A95A0D900B529A0 /* TurboNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCC50E72A95A0D900B529A0 /* TurboNavigationController.swift */; };
@@ -23,10 +30,16 @@
23
30
 
24
31
  /* Begin PBXFileReference section */
25
32
  5D0D86202A9AA70500A525E9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
33
+ 5D1F2EC02ABEB12300B2819A /* NavButtonComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavButtonComponent.swift; sourceTree = "<group>"; };
34
+ 5D281BB02ABC0C3D001CE599 /* WKWebViewConfiguration+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKWebViewConfiguration+App.swift"; sourceTree = "<group>"; };
35
+ 5D281BB32ABC0CB0001CE599 /* BridgeComponent+App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BridgeComponent+App.swift"; sourceTree = "<group>"; };
36
+ 5D281BB52ABC0CB9001CE599 /* FormComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormComponent.swift; sourceTree = "<group>"; };
37
+ 5D91C53F2AC538960046D872 /* FlashMessageComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlashMessageComponent.swift; sourceTree = "<group>"; };
38
+ 5DAF71B62AC3FB53002D04FE /* UIViewController+Toast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Toast.swift"; sourceTree = "<group>"; };
26
39
  5DCC50D02A959DF900B529A0 /* <%= name %>.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = <%= name %>.app; sourceTree = BUILT_PRODUCTS_DIR; };
27
40
  5DCC50D32A959DF900B529A0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
28
41
  5DCC50D52A959DF900B529A0 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
29
- 5DCC50D72A959DF900B529A0 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
42
+ 5DCC50D72A959DF900B529A0 /* TurboWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TurboWebViewController.swift; sourceTree = "<group>"; };
30
43
  5DCC50DC2A959DFA00B529A0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
31
44
  5DCC50DF2A959DFA00B529A0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
32
45
  5DCC50E12A959DFA00B529A0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -43,12 +56,24 @@
43
56
  buildActionMask = 2147483647;
44
57
  files = (
45
58
  5DCC50F12A95A66700B529A0 /* Turbo in Frameworks */,
59
+ 5D281BAF2ABC0BBC001CE599 /* Strada in Frameworks */,
46
60
  );
47
61
  runOnlyForDeploymentPostprocessing = 0;
48
62
  };
49
63
  /* End PBXFrameworksBuildPhase section */
50
64
 
51
65
  /* Begin PBXGroup section */
66
+ 5D281BB22ABC0C8F001CE599 /* Strada */ = {
67
+ isa = PBXGroup;
68
+ children = (
69
+ 5D281BB32ABC0CB0001CE599 /* BridgeComponent+App.swift */,
70
+ 5D281BB52ABC0CB9001CE599 /* FormComponent.swift */,
71
+ 5D1F2EC02ABEB12300B2819A /* NavButtonComponent.swift */,
72
+ 5D91C53F2AC538960046D872 /* FlashMessageComponent.swift */,
73
+ );
74
+ path = Strada;
75
+ sourceTree = "<group>";
76
+ };
52
77
  5D2FA5CD2A9DB1AB001AF2F1 /* Configuration */ = {
53
78
  isa = PBXGroup;
54
79
  children = (
@@ -77,6 +102,7 @@
77
102
  5DCC50D22A959DF900B529A0 /* <%= name %> */ = {
78
103
  isa = PBXGroup;
79
104
  children = (
105
+ 5D281BB22ABC0C8F001CE599 /* Strada */,
80
106
  5D2FA5CD2A9DB1AB001AF2F1 /* Configuration */,
81
107
  5DCC50F42A95AC4E00B529A0 /* Controllers */,
82
108
  5DCC50FB2A95B55200B529A0 /* Delegates */,
@@ -90,8 +116,10 @@
90
116
  isa = PBXGroup;
91
117
  children = (
92
118
  5DCC50ED2A95A27600B529A0 /* ErrorPresenter.swift */,
119
+ 5DAF71B62AC3FB53002D04FE /* UIViewController+Toast.swift */,
120
+ 5D281BB02ABC0C3D001CE599 /* WKWebViewConfiguration+App.swift */,
93
121
  5DCC50E72A95A0D900B529A0 /* TurboNavigationController.swift */,
94
- 5DCC50D72A959DF900B529A0 /* ViewController.swift */,
122
+ 5DCC50D72A959DF900B529A0 /* TurboWebViewController.swift */,
95
123
  5DDD58802AA9A8BE00FAC961 /* NumbersViewController.swift */,
96
124
  );
97
125
  path = Controllers;
@@ -134,6 +162,7 @@
134
162
  name = <%= name %>;
135
163
  packageProductDependencies = (
136
164
  5DCC50F02A95A66700B529A0 /* Turbo */,
165
+ 5D281BAE2ABC0BBC001CE599 /* Strada */,
137
166
  );
138
167
  productName = <%= name %>;
139
168
  productReference = 5DCC50D02A959DF900B529A0 /* <%= name %>.app */;
@@ -165,6 +194,7 @@
165
194
  mainGroup = 5DCC50C72A959DF800B529A0;
166
195
  packageReferences = (
167
196
  5DCC50EF2A95A66700B529A0 /* XCRemoteSwiftPackageReference "turbo-ios" */,
197
+ 5D281BAD2ABC0BBC001CE599 /* XCRemoteSwiftPackageReference "strada-ios" */,
168
198
  );
169
199
  productRefGroup = 5DCC50D12A959DF900B529A0 /* Products */;
170
200
  projectDirPath = "";
@@ -194,11 +224,17 @@
194
224
  isa = PBXSourcesBuildPhase;
195
225
  buildActionMask = 2147483647;
196
226
  files = (
197
- 5DCC50D82A959DF900B529A0 /* ViewController.swift in Sources */,
227
+ 5DCC50D82A959DF900B529A0 /* TurboWebViewController.swift in Sources */,
198
228
  5DDD58812AA9A8BE00FAC961 /* NumbersViewController.swift in Sources */,
229
+ 5D91C5402AC538960046D872 /* FlashMessageComponent.swift in Sources */,
230
+ 5D1F2EC12ABEB12300B2819A /* NavButtonComponent.swift in Sources */,
199
231
  5DCC50D42A959DF900B529A0 /* AppDelegate.swift in Sources */,
232
+ 5D281BB62ABC0CB9001CE599 /* FormComponent.swift in Sources */,
200
233
  5DCC50E82A95A0D900B529A0 /* TurboNavigationController.swift in Sources */,
234
+ 5DAF71B72AC3FB53002D04FE /* UIViewController+Toast.swift in Sources */,
235
+ 5D281BB12ABC0C3D001CE599 /* WKWebViewConfiguration+App.swift in Sources */,
201
236
  5DCC50D62A959DF900B529A0 /* SceneDelegate.swift in Sources */,
237
+ 5D281BB42ABC0CB0001CE599 /* BridgeComponent+App.swift in Sources */,
202
238
  5DCC50F32A95A7E600B529A0 /* <%= name %>.swift in Sources */,
203
239
  5DCC50EE2A95A27600B529A0 /* ErrorPresenter.swift in Sources */,
204
240
  );
@@ -418,17 +454,30 @@
418
454
  /* End XCConfigurationList section */
419
455
 
420
456
  /* Begin XCRemoteSwiftPackageReference section */
457
+ 5D281BAD2ABC0BBC001CE599 /* XCRemoteSwiftPackageReference "strada-ios" */ = {
458
+ isa = XCRemoteSwiftPackageReference;
459
+ repositoryURL = "https://github.com/hotwired/strada-ios";
460
+ requirement = {
461
+ kind = exactVersion;
462
+ version = "1.0.0-beta1";
463
+ };
464
+ };
421
465
  5DCC50EF2A95A66700B529A0 /* XCRemoteSwiftPackageReference "turbo-ios" */ = {
422
466
  isa = XCRemoteSwiftPackageReference;
423
467
  repositoryURL = "https://github.com/hotwired/turbo-ios";
424
468
  requirement = {
425
- kind = upToNextMajorVersion;
426
- minimumVersion = 7.0.0;
469
+ kind = exactVersion;
470
+ version = 7.0.0;
427
471
  };
428
472
  };
429
473
  /* End XCRemoteSwiftPackageReference section */
430
474
 
431
475
  /* Begin XCSwiftPackageProductDependency section */
476
+ 5D281BAE2ABC0BBC001CE599 /* Strada */ = {
477
+ isa = XCSwiftPackageProductDependency;
478
+ package = 5D281BAD2ABC0BBC001CE599 /* XCRemoteSwiftPackageReference "strada-ios" */;
479
+ productName = Strada;
480
+ };
432
481
  5DCC50F02A95A66700B529A0 /* Turbo */ = {
433
482
  isa = XCSwiftPackageProductDependency;
434
483
  package = 5DCC50EF2A95A66700B529A0 /* XCRemoteSwiftPackageReference "turbo-ios" */;
@@ -1,5 +1,14 @@
1
1
  {
2
2
  "pins" : [
3
+ {
4
+ "identity" : "strada-ios",
5
+ "kind" : "remoteSourceControl",
6
+ "location" : "https://github.com/hotwired/strada-ios",
7
+ "state" : {
8
+ "revision" : "d8c1bcf1d9511c03e7beed07c05d29f33f9b6cde",
9
+ "version" : "1.0.0-beta1"
10
+ }
11
+ },
3
12
  {
4
13
  "identity" : "turbo-ios",
5
14
  "kind" : "remoteSourceControl",
@@ -5,7 +5,7 @@
5
5
  { "patterns": ["/recede_historical_location"], "properties": { "presentation": "pop", "visitable": false } },
6
6
  { "patterns": ["/resume_historical_location"], "properties": { "presentation": "none", "visitable": false } },
7
7
  { "patterns": ["^/$"], "properties": { "presentation": "replace-all" } },
8
- { "patterns": ["/new$", "/edit$"], "properties": { "presentation": "modal" } },
8
+ { "patterns": ["/new$", "/edit$", "/signin$", "/strada-form$"], "properties": { "presentation": "modal" } },
9
9
  { "patterns": ["/numbers$"], "properties": { "view-controller": "numbers" } }
10
10
  ]
11
11
  }
@@ -30,6 +30,11 @@ class TurboNavigationController : UINavigationController {
30
30
  if isVisitable(properties) {
31
31
  visit(viewController: viewController, with: options, modal: isModal(properties))
32
32
  }
33
+
34
+ // Display notice messages natively
35
+ if let message = noticeMessage(from: url) {
36
+ presentToast(message.replacingOccurrences(of: "+", with: " "))
37
+ }
33
38
  }
34
39
  }
35
40
 
@@ -66,6 +71,10 @@ extension TurboNavigationController {
66
71
  return properties["visitable"] as? Bool ?? true
67
72
  }
68
73
 
74
+ private func noticeMessage(from url: URL) -> String? {
75
+ URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.first(where: { $0.name == "notice" })?.value
76
+ }
77
+
69
78
  private func makeViewController(for url: URL, properties: PathProperties = [:]) -> UIViewController {
70
79
  // There are many options for determining how to map urls to view controllers
71
80
  // The demo uses the path configuration for determining which view controller and presentation
@@ -81,7 +90,7 @@ extension TurboNavigationController {
81
90
  }
82
91
  }
83
92
 
84
- return ViewController(url: url)
93
+ return TurboWebViewController(url: url)
85
94
  }
86
95
 
87
96
  private func navigate(to viewController: UIViewController, action: VisitAction, properties: PathProperties = [:]) {
@@ -0,0 +1,65 @@
1
+ import UIKit
2
+ import Turbo
3
+ import Strada
4
+ import WebKit
5
+
6
+ final class TurboWebViewController: VisitableViewController, ErrorPresenter, BridgeDestination {
7
+
8
+ private lazy var bridgeDelegate: BridgeDelegate = {
9
+ BridgeDelegate(location: visitableURL.absoluteString, destination: self, componentTypes: BridgeComponent.allTypes)
10
+ }()
11
+
12
+ private lazy var dismissModalButton = {
13
+ UIBarButtonItem(image: UIImage(systemName: "chevron.down"), style: .plain, target: self, action: #selector(dismissModal))
14
+ }()
15
+
16
+ // MARK: View lifecycle
17
+
18
+ override func viewDidLoad() {
19
+ super.viewDidLoad()
20
+
21
+ navigationItem.backButtonTitle = "Back"
22
+
23
+ if presentingViewController != nil {
24
+ navigationItem.leftBarButtonItem = dismissModalButton
25
+ }
26
+
27
+ bridgeDelegate.onViewDidLoad()
28
+ }
29
+
30
+ override func viewWillAppear(_ animated: Bool) {
31
+ super.viewWillAppear(animated)
32
+ bridgeDelegate.onViewWillAppear()
33
+ }
34
+
35
+ override func viewDidAppear(_ animated: Bool) {
36
+ super.viewDidAppear(animated)
37
+ bridgeDelegate.onViewDidAppear()
38
+ }
39
+
40
+ override func viewWillDisappear(_ animated: Bool) {
41
+ super.viewWillDisappear(animated)
42
+ bridgeDelegate.onViewWillDisappear()
43
+ }
44
+
45
+ override func viewDidDisappear(_ animated: Bool) {
46
+ super.viewDidDisappear(animated)
47
+ bridgeDelegate.onViewDidDisappear()
48
+ }
49
+
50
+ // MARK: Visitable
51
+
52
+ override func visitableDidActivateWebView(_ webView: WKWebView) {
53
+ bridgeDelegate.webViewDidBecomeActive(webView)
54
+ }
55
+
56
+ override func visitableDidDeactivateWebView() {
57
+ bridgeDelegate.webViewDidBecomeDeactivated()
58
+ }
59
+
60
+ // MARK: Actions
61
+
62
+ @objc func dismissModal() {
63
+ dismiss(animated: true)
64
+ }
65
+ }
@@ -0,0 +1,59 @@
1
+ import SwiftUI
2
+
3
+ public extension UIViewController {
4
+ func presentToast(_ message: String) {
5
+ guard let root = view.window?.rootViewController else { return }
6
+
7
+ let toastView = ToastView(message: message)
8
+ toastView.translatesAutoresizingMaskIntoConstraints = false
9
+
10
+ root.view.addSubview(toastView)
11
+
12
+ NSLayoutConstraint.activate([
13
+ toastView.centerXAnchor.constraint(equalTo: root.view.centerXAnchor),
14
+ toastView.topAnchor.constraint(equalTo: root.view.safeAreaLayoutGuide.topAnchor),
15
+ toastView.widthAnchor.constraint(equalTo: root.view.safeAreaLayoutGuide.widthAnchor, constant: -10)
16
+ ])
17
+ }
18
+ }
19
+
20
+ public class ToastView: UIView {
21
+ convenience init(message: String) {
22
+ self.init(frame: .zero)
23
+ self.backgroundColor = .black
24
+ self.layer.cornerRadius = 10
25
+
26
+ let messageLabel = UILabel()
27
+ messageLabel.text = message
28
+ messageLabel.textColor = .white
29
+ messageLabel.textAlignment = .center
30
+ messageLabel.translatesAutoresizingMaskIntoConstraints = false
31
+
32
+ addSubview(messageLabel)
33
+
34
+ NSLayoutConstraint.activate([
35
+ messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 15),
36
+ messageLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -15),
37
+ messageLabel.topAnchor.constraint(equalTo: topAnchor, constant: 15),
38
+ messageLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -15)
39
+ ])
40
+
41
+ addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dismiss)))
42
+
43
+ self.alpha = .zero
44
+
45
+ UIView.animate(withDuration: 0.5, delay: .zero, animations: {
46
+ self.alpha = 0.9
47
+ }, completion: { _ in
48
+ DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { self.dismiss() }
49
+ })
50
+ }
51
+
52
+ @objc func dismiss() {
53
+ UIView.animate(withDuration: 0.5, delay: .zero, animations: {
54
+ self.alpha = .zero
55
+ }, completion: { _ in
56
+ self.removeFromSuperview()
57
+ })
58
+ }
59
+ }
@@ -0,0 +1,21 @@
1
+ import Foundation
2
+ import WebKit
3
+ import Strada
4
+
5
+ enum WebViewPool {
6
+ static var shared = WKProcessPool()
7
+ }
8
+
9
+ extension WKWebViewConfiguration {
10
+ static var appConfiguration: WKWebViewConfiguration {
11
+ let stradaSubstring = Strada.userAgentSubstring(for: BridgeComponent.allTypes)
12
+ let userAgent = "Turbo Native iOS \(stradaSubstring)"
13
+
14
+ let configuration = WKWebViewConfiguration()
15
+ configuration.processPool = WebViewPool.shared
16
+ configuration.applicationNameForUserAgent = userAgent
17
+ configuration.defaultWebpagePreferences?.preferredContentMode = .mobile
18
+
19
+ return configuration
20
+ }
21
+ }
@@ -2,6 +2,7 @@ import UIKit
2
2
  import WebKit
3
3
  import SafariServices
4
4
  import Turbo
5
+ import Strada
5
6
 
6
7
  final class SceneDelegate: UIResponder {
7
8
  private static var sharedProcessPool = WKProcessPool()
@@ -48,12 +49,11 @@ final class SceneDelegate: UIResponder {
48
49
  private lazy var modalSession = makeSession()
49
50
 
50
51
  private func makeSession() -> Session {
51
- let configuration = WKWebViewConfiguration()
52
- configuration.applicationNameForUserAgent = "Turbo Native iOS"
53
- configuration.processPool = Self.sharedProcessPool
54
-
55
- let webView = WKWebView(frame: .zero, configuration: configuration)
52
+ let webView = WKWebView(frame: .zero, configuration: .appConfiguration)
56
53
  webView.uiDelegate = self
54
+ webView.allowsLinkPreview = false
55
+
56
+ Bridge.initialize(webView) // Initialize Strada bridge.
57
57
 
58
58
  let session = Session(webView: webView)
59
59
  session.delegate = self
@@ -2,12 +2,12 @@
2
2
  "colors" : [
3
3
  {
4
4
  "color" : {
5
- "color-space" : "display-p3",
5
+ "color-space" : "srgb",
6
6
  "components" : {
7
7
  "alpha" : "1.000",
8
- "blue" : "0.898",
9
- "green" : "0.847",
10
- "red" : "0.361"
8
+ "blue" : "0.882",
9
+ "green" : "0.386",
10
+ "red" : "0.076"
11
11
  }
12
12
  },
13
13
  "idiom" : "universal"
@@ -20,12 +20,12 @@
20
20
  }
21
21
  ],
22
22
  "color" : {
23
- "color-space" : "display-p3",
23
+ "color-space" : "srgb",
24
24
  "components" : {
25
25
  "alpha" : "1.000",
26
- "blue" : "0.898",
27
- "green" : "0.847",
28
- "red" : "0.361"
26
+ "blue" : "0.882",
27
+ "green" : "0.386",
28
+ "red" : "0.076"
29
29
  }
30
30
  },
31
31
  "idiom" : "universal"
@@ -9,7 +9,7 @@
9
9
  <!--Turbo Navigation Controller-->
10
10
  <scene sceneID="GwE-Ch-DRP">
11
11
  <objects>
12
- <viewController id="cXb-wA-NUs" customClass="TurboNavigationController" customModule="TurboNativeProjectIos" customModuleProvider="target" sceneMemberID="viewController">
12
+ <viewController id="cXb-wA-NUs" customClass="TurboNavigationController" customModuleProvider="target" sceneMemberID="viewController">
13
13
  <tabBarItem key="tabBarItem" title="" image="gear" catalog="system" id="rhZ-zi-yeq"/>
14
14
  </viewController>
15
15
  <placeholder placeholderIdentifier="IBFirstResponder" id="ife-PY-okT" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
@@ -19,7 +19,7 @@
19
19
  <!--Turbo Navigation Controller-->
20
20
  <scene sceneID="pAD-IL-Hdw">
21
21
  <objects>
22
- <viewController id="0en-nE-vJA" customClass="TurboNavigationController" customModule="TurboNativeProjectIos" customModuleProvider="target" sceneMemberID="viewController">
22
+ <viewController id="0en-nE-vJA" customClass="TurboNavigationController" customModuleProvider="target" sceneMemberID="viewController">
23
23
  <tabBarItem key="tabBarItem" title="" image="house" catalog="system" id="ZWo-HO-m4a"/>
24
24
  </viewController>
25
25
  <placeholder placeholderIdentifier="IBFirstResponder" id="GRr-XE-fAM" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
@@ -0,0 +1,8 @@
1
+ import Foundation
2
+ import Strada
3
+
4
+ extension BridgeComponent {
5
+ static var allTypes: [BridgeComponent.Type] {
6
+ [FormComponent.self, NavButtonComponent.self, FlashMessageComponent.self]
7
+ }
8
+ }