turbo-native-initializer 0.0.10 → 0.0.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -1
- data/lib/turbo_native_initializer/generator.rb +1 -0
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/base/NavDestination.kt.tt +17 -2
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/features/web/WebFragment.kt.tt +38 -0
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/features/web/WebModalFragment.kt.tt +2 -2
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/main/MainActivity.kt.tt +7 -0
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/main/MainSessionNavHostFragment.kt.tt +5 -5
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/BridgeComponentFactories.kt.tt +9 -0
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FlashMessageComponent.kt.tt +41 -0
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FormComponent.kt.tt +87 -0
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/NavButtonComponent.kt.tt +70 -0
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/util/Extension.kt.tt +19 -0
- data/lib/turbo_native_initializer/templates/android_stack/base/app/build.gradle.kts.tt +9 -3
- data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/assets/json/configuration.json +1 -1
- data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/form_component_submit.xml +12 -0
- data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/nav_button_component.xml +14 -0
- data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/values/colors.xml +1 -1
- data/lib/turbo_native_initializer/templates/android_stack/base/build.gradle.kts +2 -1
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/base/NavDestination.kt.tt +17 -2
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/features/web/WebFragment.kt.tt +38 -0
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/features/web/WebModalFragment.kt.tt +2 -2
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/main/BaseSessionNavHostFragment.kt.tt +5 -5
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/main/MainActivity.kt.tt +4 -0
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/BridgeComponentFactories.kt.tt +9 -0
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FlashMessageComponent.kt.tt +41 -0
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FormComponent.kt.tt +87 -0
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/NavButtonComponent.kt.tt +70 -0
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/util/Extension.kt.tt +19 -0
- data/lib/turbo_native_initializer/templates/android_tabs/base/app/build.gradle.kts.tt +9 -3
- data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/assets/json/configuration.json +1 -1
- data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/form_component_submit.xml +12 -0
- data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/nav_button_component.xml +14 -0
- data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/values/colors.xml +1 -1
- data/lib/turbo_native_initializer/templates/android_tabs/base/build.gradle.kts +2 -1
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Configuration/path-configuration.json +1 -1
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/TurboNavigationController.swift +12 -8
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/TurboWebViewController.swift +65 -0
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/UIViewController+Toast.swift +59 -0
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/WKWebViewConfiguration+App.swift +21 -0
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Delegates/SceneDelegate.swift.tt +4 -5
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Resources/Assets.xcassets/AccentColor.colorset/Contents.json +8 -8
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Resources/Base.lproj/{Main.storyboard.tt → Main.storyboard} +1 -1
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/BridgeComponent+App.swift +8 -0
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/FlashMessageComponent.swift +47 -0
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/FormComponent.swift +78 -0
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/NavButtonComponent.swift +60 -0
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject.xcodeproj/project.pbxproj.tt +55 -6
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +9 -0
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Configuration/path-configuration.json +1 -1
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/TurboNavigationController.swift +11 -8
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/TurboWebViewController.swift +65 -0
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/UIViewController+Toast.swift +59 -0
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/WKWebViewConfiguration+App.swift +21 -0
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Delegates/SceneDelegate.swift.tt +4 -5
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Resources/Assets.xcassets/AccentColor.colorset/Contents.json +8 -8
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Resources/Base.lproj/Main.storyboard +2 -2
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/BridgeComponent+App.swift +8 -0
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/FlashMessageComponent.swift +47 -0
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/FormComponent.swift +78 -0
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/NavButtonComponent.swift +60 -0
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject.xcodeproj/project.pbxproj.tt +55 -6
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +9 -0
- data/lib/turbo_native_initializer/version.rb +1 -1
- metadata +32 -6
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/ViewController.swift +0 -22
- 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 =
|
27
|
-
}
|
27
|
+
session.webView.settings.userAgentString = session.webView.customUserAgent
|
28
28
|
|
29
|
-
|
30
|
-
|
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.
|
29
|
-
targetCompatibility = JavaVersion.
|
29
|
+
sourceCompatibility = JavaVersion.VERSION_17
|
30
|
+
targetCompatibility = JavaVersion.VERSION_17
|
30
31
|
}
|
31
32
|
kotlinOptions {
|
32
|
-
jvmTarget = "
|
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>
|
@@ -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
|
}
|
@@ -22,19 +22,19 @@ class TurboNavigationController : UINavigationController {
|
|
22
22
|
}
|
23
23
|
|
24
24
|
// - Create view controller appropriate for url/properties
|
25
|
-
let viewController = makeViewController(for: url, properties: properties)
|
26
|
-
|
27
25
|
// - Navigate to that with the correct presentation
|
28
|
-
|
29
|
-
|
30
|
-
} else {
|
31
|
-
navigate(to: viewController, action: .replace, properties: properties)
|
32
|
-
}
|
26
|
+
let viewController = makeViewController(for: url, properties: properties)
|
27
|
+
navigate(to: viewController, action: options.action, properties: properties)
|
33
28
|
|
34
29
|
// Initiate the visit with Turbo
|
35
30
|
if isVisitable(properties) {
|
36
31
|
visit(viewController: viewController, with: options, modal: isModal(properties))
|
37
32
|
}
|
33
|
+
|
34
|
+
// Display notice messages natively
|
35
|
+
if let message = noticeMessage(from: url) {
|
36
|
+
presentToast(message.replacingOccurrences(of: "+", with: " "))
|
37
|
+
}
|
38
38
|
}
|
39
39
|
}
|
40
40
|
|
@@ -71,6 +71,10 @@ extension TurboNavigationController {
|
|
71
71
|
return properties["visitable"] as? Bool ?? true
|
72
72
|
}
|
73
73
|
|
74
|
+
private func noticeMessage(from url: URL) -> String? {
|
75
|
+
URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.first(where: { $0.name == "notice" })?.value
|
76
|
+
}
|
77
|
+
|
74
78
|
private func makeViewController(for url: URL, properties: PathProperties = [:]) -> UIViewController {
|
75
79
|
// There are many options for determining how to map urls to view controllers
|
76
80
|
// The demo uses the path configuration for determining which view controller and presentation
|
@@ -86,7 +90,7 @@ extension TurboNavigationController {
|
|
86
90
|
}
|
87
91
|
}
|
88
92
|
|
89
|
-
return
|
93
|
+
return TurboWebViewController(url: url)
|
90
94
|
}
|
91
95
|
|
92
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,14 +35,12 @@ final class SceneDelegate: UIResponder {
|
|
34
35
|
private lazy var modalSession = makeSession()
|
35
36
|
|
36
37
|
private func makeSession() -> Session {
|
37
|
-
let
|
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
|
43
40
|
webView.allowsLinkPreview = false
|
44
41
|
|
42
|
+
Bridge.initialize(webView) // Initialize Strada bridge.
|
43
|
+
|
45
44
|
let session = Session(webView: webView)
|
46
45
|
session.delegate = self
|
47
46
|
session.pathConfiguration = pathConfiguration
|
@@ -2,12 +2,12 @@
|
|
2
2
|
"colors" : [
|
3
3
|
{
|
4
4
|
"color" : {
|
5
|
-
"color-space" : "
|
5
|
+
"color-space" : "srgb",
|
6
6
|
"components" : {
|
7
7
|
"alpha" : "1.000",
|
8
|
-
"blue" : "0.
|
9
|
-
"green" : "0.
|
10
|
-
"red" : "0.
|
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" : "
|
23
|
+
"color-space" : "srgb",
|
24
24
|
"components" : {
|
25
25
|
"alpha" : "1.000",
|
26
|
-
"blue" : "0.
|
27
|
-
"green" : "0.
|
28
|
-
"red" : "0.
|
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"
|
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"/>
|