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
@@ -1,14 +1,15 @@
1
1
  package <%= package_name %>.main
2
2
 
3
- import android.webkit.WebView
4
3
  import androidx.appcompat.app.AppCompatActivity
5
4
  import androidx.fragment.app.Fragment
5
+ import dev.hotwire.strada.Bridge
6
6
  import dev.hotwire.turbo.config.TurboPathConfiguration
7
7
  import <%= package_name %>.features.native.NumbersFragment
8
8
  import <%= package_name %>.features.web.WebFragment
9
9
  import <%= package_name %>.features.web.WebHomeFragment
10
10
  import <%= package_name %>.features.web.WebModalFragment
11
11
  import dev.hotwire.turbo.session.TurboSessionNavHostFragment
12
+ import <%= package_name %>.util.customUserAgent
12
13
  import kotlin.reflect.KClass
13
14
 
14
15
  abstract class BaseSessionNavHostFragment : TurboSessionNavHostFragment() {
@@ -23,10 +24,9 @@ abstract class BaseSessionNavHostFragment : TurboSessionNavHostFragment() {
23
24
 
24
25
  override fun onSessionCreated() {
25
26
  super.onSessionCreated()
26
- session.webView.settings.userAgentString = customUserAgent(session.webView)
27
- }
27
+ session.webView.settings.userAgentString = session.webView.customUserAgent
28
28
 
29
- private fun customUserAgent(webView: WebView): String {
30
- return "Turbo Native Android ${webView.settings.userAgentString}"
29
+ // Initialize Strada bridge with new WebView instance
30
+ Bridge.initialize(session.webView)
31
31
  }
32
32
  }
@@ -4,6 +4,8 @@ import android.os.Bundle
4
4
  import android.widget.ViewFlipper
5
5
  import androidx.appcompat.app.AppCompatActivity
6
6
  import com.google.android.material.bottomnavigation.BottomNavigationView
7
+ import dev.hotwire.strada.KotlinXJsonConverter
8
+ import dev.hotwire.strada.Strada
7
9
  import dev.hotwire.turbo.activities.TurboActivity
8
10
  import dev.hotwire.turbo.delegates.TurboActivityDelegate
9
11
  import <%= package_name %>.R
@@ -25,6 +27,8 @@ class MainActivity : AppCompatActivity(), TurboActivity {
25
27
  delegate.registerNavHostFragment(R.id.tab_one_nav_host)
26
28
  delegate.registerNavHostFragment(R.id.tab_two_nav_host)
27
29
 
30
+ Strada.config.jsonConverter = KotlinXJsonConverter()
31
+
28
32
  setupBottomNavigationView()
29
33
  }
30
34
 
@@ -0,0 +1,9 @@
1
+ package <%= package_name %>.strada
2
+
3
+ import dev.hotwire.strada.BridgeComponentFactory
4
+
5
+ val bridgeComponentFactories = listOf(
6
+ BridgeComponentFactory("form", ::FormComponent),
7
+ BridgeComponentFactory("nav-button", ::NavButtonComponent),
8
+ BridgeComponentFactory("flash-message", ::FlashMessageComponent),
9
+ )
@@ -0,0 +1,41 @@
1
+ package <%= package_name %>.strada
2
+
3
+ import android.util.Log
4
+ import androidx.fragment.app.Fragment
5
+ import com.google.android.material.snackbar.Snackbar
6
+ import dev.hotwire.strada.BridgeComponent
7
+ import dev.hotwire.strada.BridgeDelegate
8
+ import dev.hotwire.strada.Message
9
+ import <%= package_name %>.base.NavDestination
10
+ import kotlinx.serialization.Serializable
11
+
12
+ class FlashMessageComponent(
13
+ name: String,
14
+ private val delegate: BridgeDelegate<NavDestination>
15
+ ) : BridgeComponent<NavDestination>(name, delegate) {
16
+
17
+ private val fragment: Fragment
18
+ get() = delegate.destination.fragment
19
+
20
+ override fun onReceive(message: Message) {
21
+ if (message.event == "connect") {
22
+ handleConnectEvent(message)
23
+ } else {
24
+ Log.w("TurboNative", "Unknown event for message: $message")
25
+ }
26
+ }
27
+
28
+ private fun handleConnectEvent(message: Message) {
29
+ val data = message.data<MessageData>() ?: return
30
+ showSnackBar(data)
31
+ }
32
+
33
+ private fun showSnackBar(data: MessageData) {
34
+ Snackbar.make(fragment.requireView(), data.title, Snackbar.LENGTH_SHORT).show()
35
+ }
36
+
37
+ @Serializable
38
+ data class MessageData(
39
+ val title: String
40
+ )
41
+ }
@@ -0,0 +1,87 @@
1
+ package <%= package_name %>.strada
2
+
3
+ import android.util.Log
4
+ import android.view.LayoutInflater
5
+ import android.view.Menu
6
+ import android.view.MenuItem
7
+ import androidx.appcompat.widget.Toolbar
8
+ import androidx.fragment.app.Fragment
9
+ import dev.hotwire.strada.BridgeComponent
10
+ import dev.hotwire.strada.BridgeDelegate
11
+ import dev.hotwire.strada.Message
12
+ import <%= package_name %>.R
13
+ import <%= package_name %>.base.NavDestination
14
+ import <%= package_name %>.databinding.FormComponentSubmitBinding
15
+ import kotlinx.serialization.Serializable
16
+
17
+ class FormComponent(
18
+ name: String,
19
+ private val delegate: BridgeDelegate<NavDestination>
20
+ ) : BridgeComponent<NavDestination>(name, delegate) {
21
+
22
+ private val submitButtonItemId = 10
23
+ private var submitMenuItem: MenuItem? = null
24
+ private val fragment: Fragment
25
+ get() = delegate.destination.fragment
26
+ private val toolbar: Toolbar?
27
+ get() = fragment.view?.findViewById(R.id.toolbar)
28
+
29
+ override fun onReceive(message: Message) {
30
+ when (message.event) {
31
+ "connect" -> handleConnectEvent(message)
32
+ "submitEnabled" -> handleSubmitEnabled()
33
+ "submitDisabled" -> handleSubmitDisabled()
34
+ else -> Log.w("TurboNative", "Unknown event for message: $message")
35
+ }
36
+ }
37
+
38
+ private fun handleConnectEvent(message: Message) {
39
+ val data = message.data<MessageData>() ?: return
40
+ showToolbarButton(data)
41
+ }
42
+
43
+ private fun handleSubmitEnabled() {
44
+ toggleSubmitButton(true)
45
+ }
46
+
47
+ private fun handleSubmitDisabled() {
48
+ toggleSubmitButton(false)
49
+ }
50
+
51
+ private fun showToolbarButton(data: MessageData) {
52
+ val menu = toolbar?.menu ?: return
53
+ val inflater = LayoutInflater.from(fragment.requireContext())
54
+ val binding = FormComponentSubmitBinding.inflate(inflater)
55
+ val order = 999 // Show as the right-most button
56
+
57
+ binding.formSubmit.apply {
58
+ text = data.title
59
+ setOnClickListener {
60
+ performSubmit()
61
+ }
62
+ }
63
+
64
+ menu.removeItem(submitButtonItemId)
65
+ submitMenuItem = menu.add(Menu.NONE, submitButtonItemId, order, data.title).apply {
66
+ actionView = binding.root
67
+ setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
68
+ }
69
+ }
70
+
71
+ private fun toggleSubmitButton(enable: Boolean) {
72
+ val layout = submitMenuItem?.actionView ?: return
73
+
74
+ FormComponentSubmitBinding.bind(layout).apply {
75
+ formSubmit.isEnabled = enable
76
+ }
77
+ }
78
+
79
+ private fun performSubmit(): Boolean {
80
+ return replyTo("connect")
81
+ }
82
+
83
+ @Serializable
84
+ data class MessageData(
85
+ val title: String
86
+ )
87
+ }
@@ -0,0 +1,70 @@
1
+ package <%= package_name %>.strada
2
+
3
+ import android.util.Log
4
+ import android.view.LayoutInflater
5
+ import android.view.Menu
6
+ import android.view.MenuItem
7
+ import androidx.appcompat.widget.Toolbar
8
+ import androidx.fragment.app.Fragment
9
+ import dev.hotwire.strada.BridgeComponent
10
+ import dev.hotwire.strada.BridgeDelegate
11
+ import dev.hotwire.strada.Message
12
+ import <%= package_name %>.R
13
+ import <%= package_name %>.base.NavDestination
14
+ import <%= package_name %>.databinding.NavButtonComponentBinding
15
+ import kotlinx.serialization.Serializable
16
+
17
+ class NavButtonComponent(
18
+ name: String,
19
+ private val delegate: BridgeDelegate<NavDestination>
20
+ ) : BridgeComponent<NavDestination>(name, delegate) {
21
+
22
+ private val navButtonItemId = 20
23
+ private var navButtonMenuItem: MenuItem? = null
24
+ private val fragment: Fragment
25
+ get() = delegate.destination.fragment
26
+ private val toolbar: Toolbar?
27
+ get() = fragment.view?.findViewById(R.id.toolbar)
28
+
29
+ override fun onReceive(message: Message) {
30
+ if (message.event == "connect") {
31
+ handleConnectEvent(message)
32
+ } else {
33
+ Log.w("TurboNative", "Unknown event for message: $message")
34
+ }
35
+ }
36
+
37
+ private fun handleConnectEvent(message: Message) {
38
+ val data = message.data<MessageData>() ?: return
39
+ showToolbarButton(data)
40
+ }
41
+
42
+ private fun showToolbarButton(data: MessageData) {
43
+ val menu = toolbar?.menu ?: return
44
+ val inflater = LayoutInflater.from(fragment.requireContext())
45
+ val binding = NavButtonComponentBinding.inflate(inflater)
46
+ val order = 999 // Show as the right-most button
47
+
48
+ binding.navButton.apply {
49
+ text = data.title
50
+ setOnClickListener {
51
+ performAction()
52
+ }
53
+ }
54
+
55
+ menu.removeItem(navButtonItemId)
56
+ navButtonMenuItem = menu.add(Menu.NONE, navButtonItemId, order, data.title).apply {
57
+ actionView = binding.root
58
+ setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
59
+ }
60
+ }
61
+
62
+ private fun performAction(): Boolean {
63
+ return replyTo("connect")
64
+ }
65
+
66
+ @Serializable
67
+ data class MessageData(
68
+ val title: String
69
+ )
70
+ }
@@ -0,0 +1,19 @@
1
+ package <%= package_name %>.util
2
+
3
+ import android.webkit.WebView
4
+ import androidx.appcompat.widget.Toolbar
5
+ import androidx.core.content.ContextCompat
6
+ import dev.hotwire.strada.Strada
7
+ import <%= package_name %>.R
8
+ import <%= package_name %>.strada.bridgeComponentFactories
9
+
10
+ fun Toolbar.displayBackButtonAsCloseIcon() {
11
+ navigationIcon = ContextCompat.getDrawable(context, R.drawable.ic_close)
12
+ }
13
+
14
+ val WebView.customUserAgent: String
15
+ get() {
16
+ val turboSubstring = "Turbo Native Android"
17
+ val stradaSubstring = Strada.userAgentSubstring(bridgeComponentFactories)
18
+ return "$turboSubstring; $stradaSubstring; ${settings.userAgentString}"
19
+ }
@@ -1,6 +1,7 @@
1
1
  plugins {
2
2
  id("com.android.application")
3
3
  id("org.jetbrains.kotlin.android")
4
+ id("org.jetbrains.kotlin.plugin.serialization")
4
5
  }
5
6
 
6
7
  android {
@@ -25,13 +26,14 @@ android {
25
26
  }
26
27
  }
27
28
  compileOptions {
28
- sourceCompatibility = JavaVersion.VERSION_1_8
29
- targetCompatibility = JavaVersion.VERSION_1_8
29
+ sourceCompatibility = JavaVersion.VERSION_17
30
+ targetCompatibility = JavaVersion.VERSION_17
30
31
  }
31
32
  kotlinOptions {
32
- jvmTarget = "1.8"
33
+ jvmTarget = "17"
33
34
  }
34
35
  buildFeatures {
36
+ viewBinding = true
35
37
  compose = true
36
38
  }
37
39
  composeOptions {
@@ -54,4 +56,8 @@ dependencies {
54
56
 
55
57
  // Turbo Android
56
58
  implementation("dev.hotwire:turbo:7.0.0")
59
+
60
+ // Strada
61
+ implementation("dev.hotwire:strada:1.0.0-beta2")
62
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
57
63
  }
@@ -6,7 +6,7 @@
6
6
  { "patterns": ["/recede_historical_location"], "properties": { "presentation": "pop" } },
7
7
  { "patterns": ["/resume_historical_location"], "properties": { "presentation": "none" } },
8
8
  { "patterns": ["^/$"], "properties": { "uri": "turbo://fragment/web/home", "presentation": "replace_all" } },
9
- { "patterns": ["/new$", "/edit$"], "properties": { "context": "modal", "uri": "turbo://fragment/web/modal" } },
9
+ { "patterns": ["/new$", "/edit$", "/signin$", "/strada-form$"], "properties": { "context": "modal", "uri": "turbo://fragment/web/modal", "pull_to_refresh_enabled": false } },
10
10
  { "patterns": ["/numbers$"], "properties": { "uri": "turbo://fragment/numbers", "title": "Numbers" } }
11
11
  ]
12
12
  }
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+ android:layout_width="wrap_content"
4
+ android:layout_height="match_parent"
5
+ android:layout_gravity="end|center_vertical"
6
+ android:paddingEnd="16dp">
7
+
8
+ <com.google.android.material.button.MaterialButton
9
+ android:id="@+id/form_submit"
10
+ android:layout_width="wrap_content"
11
+ android:layout_height="48dp" />
12
+ </FrameLayout>
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+ android:layout_width="wrap_content"
4
+ android:layout_height="match_parent"
5
+ android:layout_gravity="end|center_vertical"
6
+ android:paddingEnd="16dp">
7
+
8
+ <com.google.android.material.button.MaterialButton
9
+ style="@style/Widget.Material3.Button.TextButton"
10
+ android:id="@+id/nav_button"
11
+ android:layout_width="wrap_content"
12
+ android:layout_height="48dp"
13
+ android:minWidth="0dip" />
14
+ </FrameLayout>
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0" encoding="utf-8"?>
2
2
  <resources>
3
- <color name="color_brand">#5cd8e5</color>
3
+ <color name="color_brand">#1362E0</color>
4
4
  <color name="black">#FF000000</color>
5
5
  <color name="white">#FFFFFFFF</color>
6
6
  </resources>
@@ -2,4 +2,5 @@
2
2
  plugins {
3
3
  id("com.android.application") version "8.1.1" apply false
4
4
  id("org.jetbrains.kotlin.android") version "1.9.0" apply false
5
- }
5
+ id("org.jetbrains.kotlin.plugin.serialization") version "1.9.0" apply false
6
+ }
@@ -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$", "/signin$"], "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()
@@ -34,12 +35,11 @@ final class SceneDelegate: UIResponder {
34
35
  private lazy var modalSession = makeSession()
35
36
 
36
37
  private func makeSession() -> Session {
37
- let configuration = WKWebViewConfiguration()
38
- configuration.applicationNameForUserAgent = "Turbo Native iOS"
39
- configuration.processPool = Self.sharedProcessPool
40
-
41
- let webView = WKWebView(frame: .zero, configuration: configuration)
38
+ let webView = WKWebView(frame: .zero, configuration: .appConfiguration)
42
39
  webView.uiDelegate = self
40
+ webView.allowsLinkPreview = false
41
+
42
+ Bridge.initialize(webView) // Initialize Strada bridge.
43
43
 
44
44
  let session = Session(webView: webView)
45
45
  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"
@@ -8,7 +8,7 @@
8
8
  <!--Turbo Navigation Controller-->
9
9
  <scene sceneID="s0d-6b-0kx">
10
10
  <objects>
11
- <viewController id="Y6W-OH-hqX" customClass="TurboNavigationController" customModule="TurboNativeProjectIos" customModuleProvider="target" sceneMemberID="viewController"/>
11
+ <viewController id="Y6W-OH-hqX" customClass="TurboNavigationController" customModuleProvider="target" sceneMemberID="viewController"/>
12
12
  <placeholder placeholderIdentifier="IBFirstResponder" id="Ief-a0-LHa" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
13
13
  </objects>
14
14
  <point key="canvasLocation" x="130" y="-2"/>
@@ -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
+ }