turbo-native-initializer 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/lib/turbo_native_initializer/generator.rb +1 -0
  4. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/base/NavDestination.kt.tt +17 -2
  5. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/features/web/WebFragment.kt.tt +38 -0
  6. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/features/web/WebModalFragment.kt.tt +2 -2
  7. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/main/MainActivity.kt.tt +7 -0
  8. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/main/MainSessionNavHostFragment.kt.tt +5 -5
  9. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/BridgeComponentFactories.kt.tt +9 -0
  10. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FlashMessageComponent.kt.tt +41 -0
  11. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FormComponent.kt.tt +87 -0
  12. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/NavButtonComponent.kt.tt +70 -0
  13. data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/util/Extension.kt.tt +19 -0
  14. data/lib/turbo_native_initializer/templates/android_stack/base/app/build.gradle.kts.tt +9 -3
  15. data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/assets/json/configuration.json +1 -1
  16. data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/form_component_submit.xml +12 -0
  17. data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/nav_button_component.xml +14 -0
  18. data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/values/colors.xml +1 -1
  19. data/lib/turbo_native_initializer/templates/android_stack/base/build.gradle.kts +2 -1
  20. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/base/NavDestination.kt.tt +17 -2
  21. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/features/web/WebFragment.kt.tt +38 -0
  22. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/features/web/WebModalFragment.kt.tt +2 -2
  23. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/main/BaseSessionNavHostFragment.kt.tt +5 -5
  24. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/main/MainActivity.kt.tt +4 -0
  25. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/BridgeComponentFactories.kt.tt +9 -0
  26. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FlashMessageComponent.kt.tt +41 -0
  27. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FormComponent.kt.tt +87 -0
  28. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/NavButtonComponent.kt.tt +70 -0
  29. data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/util/Extension.kt.tt +19 -0
  30. data/lib/turbo_native_initializer/templates/android_tabs/base/app/build.gradle.kts.tt +9 -3
  31. data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/assets/json/configuration.json +1 -1
  32. data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/form_component_submit.xml +12 -0
  33. data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/nav_button_component.xml +14 -0
  34. data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/values/colors.xml +1 -1
  35. data/lib/turbo_native_initializer/templates/android_tabs/base/build.gradle.kts +2 -1
  36. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Configuration/path-configuration.json +1 -1
  37. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/TurboNavigationController.swift +12 -8
  38. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/TurboWebViewController.swift +65 -0
  39. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/UIViewController+Toast.swift +79 -0
  40. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/WKWebViewConfiguration+App.swift +21 -0
  41. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Delegates/SceneDelegate.swift.tt +4 -5
  42. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Resources/Assets.xcassets/AccentColor.colorset/Contents.json +8 -8
  43. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Resources/Base.lproj/{Main.storyboard.tt → Main.storyboard} +1 -1
  44. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/BridgeComponent+App.swift +8 -0
  45. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/FlashMessageComponent.swift +47 -0
  46. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/FormComponent.swift +78 -0
  47. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/NavButtonComponent.swift +60 -0
  48. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject.xcodeproj/project.pbxproj.tt +55 -6
  49. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +9 -0
  50. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Configuration/path-configuration.json +1 -1
  51. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/TurboNavigationController.swift +11 -8
  52. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/TurboWebViewController.swift +65 -0
  53. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/UIViewController+Toast.swift +79 -0
  54. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/WKWebViewConfiguration+App.swift +21 -0
  55. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Delegates/SceneDelegate.swift.tt +4 -5
  56. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Resources/Assets.xcassets/AccentColor.colorset/Contents.json +8 -8
  57. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Resources/Base.lproj/Main.storyboard +2 -2
  58. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/BridgeComponent+App.swift +8 -0
  59. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/FlashMessageComponent.swift +47 -0
  60. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/FormComponent.swift +78 -0
  61. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/NavButtonComponent.swift +60 -0
  62. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject.xcodeproj/project.pbxproj.tt +55 -6
  63. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +9 -0
  64. data/lib/turbo_native_initializer/version.rb +1 -1
  65. metadata +32 -6
  66. data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/ViewController.swift +0 -22
  67. data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/ViewController.swift +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52ff35d610a3d846038f5e159c46a04fe6c9426d4975b09654c2a55744b9ae9d
4
- data.tar.gz: 4646660e51e7e5e6b0721a19f9b74506305a5f119c2d3afe29310991f82fc7e6
3
+ metadata.gz: c1f5d9801c82d83f38a465a6375d87b1b90ac03a19bcf292b78555f9bc925cee
4
+ data.tar.gz: 5405c39714a62d1f5823d46be8bb9935ac52743f2c02d9a0b544b82f73fd6f40
5
5
  SHA512:
6
- metadata.gz: 5c147b87af14885a7561e407875cbf2b417cd5368409c0ee5e48b1a200c058500e565bd373e922f0e548fa5c27d9923fed2fd93353ea4cb3ac7478f28a38cb3b
7
- data.tar.gz: b654caa260551c993b93fbb31d52e68ba38e2ae8959c55f6205df49972bf0db81e87482724f5f0fac10e84b0c5c90edc1eb47c4e1bb71b43feb6ea6280a7ec3b
6
+ metadata.gz: 65fbcd8000abe1b6c9c4c0c0ca4ab715ecbc8ac828ca1b859bc93b7d9a32718163913b12b881ee6ea2e41c244df50c7d869dc8fe420d86a09e9dfcd50b274a28
7
+ data.tar.gz: d3c907983b7c31767b99c9bd34bb0b0185fbb4bb8028c19a6c5e6648c739baf99a08e6a4318207ed25644a41c2a6d4d84bcabd529288dd85004c42b52f21f0a4
data/README.md CHANGED
@@ -11,6 +11,8 @@ A turbo native project generator for iOS and Android.
11
11
  - Added presentations `pop`, `refresh`, `none`, `replace`, `clear-all`, and `replace-all`. (iOS)
12
12
  - Added `visitable` property in order to avoid visits. (iOS)
13
13
  - Added support for tab navigation. (iOS/Android)
14
+ - Added support for flash messages. (iOS/Android)
15
+ - Added navigation bar button component. (iOS/Android)
14
16
 
15
17
  ## Installation
16
18
 
@@ -56,7 +58,7 @@ Options:
56
58
 
57
59
  ## Development
58
60
 
59
- After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
61
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment, you can run something like `TurboNativeInitializer::Generator.start(["TurboNativeProject", "--platform=ios", "--navigation=stack"])`.
60
62
 
61
63
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
62
64
 
@@ -23,6 +23,7 @@ module TurboNativeInitializer
23
23
  directory "#{project}/TurboNativeProject/Controllers", "#{name}/#{name}/Controllers"
24
24
  directory "#{project}/TurboNativeProject/Delegates", "#{name}/#{name}/Delegates"
25
25
  directory "#{project}/TurboNativeProject/Resources", "#{name}/#{name}/Resources"
26
+ directory "#{project}/TurboNativeProject/Strada", "#{name}/#{name}/Strada"
26
27
  directory "#{project}/TurboNativeProject.xcodeproj", "#{name}/#{name}.xcodeproj"
27
28
  template "#{project}/TurboNativeProject/TurboNativeProject.swift", "#{name}/#{name}/#{name}.swift"
28
29
  when "android"
@@ -6,17 +6,20 @@ import androidx.browser.customtabs.CustomTabsIntent
6
6
  import androidx.browser.customtabs.CustomTabsIntent.SHARE_STATE_ON
7
7
  import androidx.navigation.NavOptions
8
8
  import androidx.navigation.navOptions
9
+ import com.google.android.material.snackbar.Snackbar
10
+ import dev.hotwire.strada.BridgeDestination
9
11
  import dev.hotwire.turbo.config.TurboPathConfigurationProperties
10
12
  import dev.hotwire.turbo.config.context
11
13
  import <%= package_name %>.R
12
14
  import <%= package_name %>.util.BASE_URL
13
15
  import dev.hotwire.turbo.nav.TurboNavDestination
16
+ import dev.hotwire.turbo.nav.TurboNavPresentationContext.DEFAULT
14
17
  import dev.hotwire.turbo.nav.TurboNavPresentationContext.MODAL
15
18
 
16
- interface NavDestination : TurboNavDestination {
19
+ interface NavDestination : TurboNavDestination, BridgeDestination {
17
20
  override fun shouldNavigateTo(newLocation: String): Boolean {
18
21
  return when (isNavigable(newLocation)) {
19
- true -> true
22
+ true -> { displayNoticeMessage(newLocation); true }
20
23
  else -> { launchCustomTab(newLocation); false }
21
24
  }
22
25
  }
@@ -31,10 +34,22 @@ interface NavDestination : TurboNavDestination {
31
34
  }
32
35
  }
33
36
 
37
+ override fun bridgeWebViewIsReady(): Boolean {
38
+ return session.isReady
39
+ }
40
+
34
41
  private fun isNavigable(location: String): Boolean {
35
42
  return location.startsWith(BASE_URL)
36
43
  }
37
44
 
45
+ private fun displayNoticeMessage(location: String) {
46
+ var message = Uri.parse(location).getQueryParameter("notice")
47
+
48
+ if (pathProperties.context == DEFAULT && message != null) {
49
+ Snackbar.make(fragment.requireView(), message, Snackbar.LENGTH_SHORT).show()
50
+ }
51
+ }
52
+
38
53
  private fun launchCustomTab(location: String) {
39
54
  val context = fragment.context ?: return
40
55
  val color = context.getColor(R.color.white)
@@ -1,16 +1,54 @@
1
1
  package <%= package_name %>.features.web
2
2
 
3
+ import android.os.Bundle
3
4
  import android.view.View
5
+ import dev.hotwire.strada.BridgeDelegate
4
6
  import <%= package_name %>.base.NavDestination
5
7
  import <%= package_name %>.util.SIGN_IN_URL
6
8
  import dev.hotwire.turbo.fragments.TurboWebFragment
7
9
  import dev.hotwire.turbo.nav.TurboNavGraphDestination
8
10
  import <%= package_name %>.R
11
+ import <%= package_name %>.strada.bridgeComponentFactories
12
+ import dev.hotwire.turbo.views.TurboWebView
9
13
  import dev.hotwire.turbo.visit.TurboVisitAction.REPLACE
10
14
  import dev.hotwire.turbo.visit.TurboVisitOptions
11
15
 
12
16
  @TurboNavGraphDestination(uri = "turbo://fragment/web")
13
17
  open class WebFragment : TurboWebFragment(), NavDestination {
18
+ private val bridgeDelegate by lazy {
19
+ BridgeDelegate(
20
+ location = location,
21
+ destination = this,
22
+ componentFactories = bridgeComponentFactories
23
+ )
24
+ }
25
+
26
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
27
+ super.onViewCreated(view, savedInstanceState)
28
+ viewLifecycleOwner.lifecycle.addObserver(bridgeDelegate)
29
+ }
30
+
31
+ override fun onDestroyView() {
32
+ super.onDestroyView()
33
+ viewLifecycleOwner.lifecycle.removeObserver(bridgeDelegate)
34
+ }
35
+
36
+ override fun onColdBootPageStarted(location: String) {
37
+ bridgeDelegate.onColdBootPageStarted()
38
+ }
39
+
40
+ override fun onColdBootPageCompleted(location: String) {
41
+ bridgeDelegate.onColdBootPageCompleted()
42
+ }
43
+
44
+ override fun onWebViewAttached(webView: TurboWebView) {
45
+ bridgeDelegate.onWebViewAttached(webView)
46
+ }
47
+
48
+ override fun onWebViewDetached(webView: TurboWebView) {
49
+ bridgeDelegate.onWebViewDetached()
50
+ }
51
+
14
52
  override fun onVisitErrorReceived(location: String, errorCode: Int) {
15
53
  when (errorCode) {
16
54
  401 -> navigate(SIGN_IN_URL, TurboVisitOptions(action = REPLACE))
@@ -3,7 +3,7 @@ package <%= package_name %>.features.web
3
3
  import android.os.Bundle
4
4
  import android.view.View
5
5
  import dev.hotwire.turbo.nav.TurboNavGraphDestination
6
- import <%= package_name %>.R
6
+ import <%= package_name %>.util.displayBackButtonAsCloseIcon
7
7
 
8
8
  @TurboNavGraphDestination(uri = "turbo://fragment/web/modal")
9
9
  class WebModalFragment : WebFragment() {
@@ -12,6 +12,6 @@ class WebModalFragment : WebFragment() {
12
12
  }
13
13
 
14
14
  private fun initToolbar() {
15
- toolbarForNavigation()?.navigationIcon = context?.getDrawable(R.drawable.ic_close)
15
+ toolbarForNavigation()?.displayBackButtonAsCloseIcon()
16
16
  }
17
17
  }
@@ -2,6 +2,8 @@ package <%= package_name %>.main
2
2
 
3
3
  import android.os.Bundle
4
4
  import androidx.appcompat.app.AppCompatActivity
5
+ import dev.hotwire.strada.KotlinXJsonConverter
6
+ import dev.hotwire.strada.Strada
5
7
  import dev.hotwire.turbo.activities.TurboActivity
6
8
  import dev.hotwire.turbo.delegates.TurboActivityDelegate
7
9
  import <%= package_name %>.R
@@ -14,5 +16,10 @@ class MainActivity : AppCompatActivity(), TurboActivity {
14
16
  setContentView(R.layout.activity_main)
15
17
 
16
18
  delegate = TurboActivityDelegate(this, R.id.main_nav_host)
19
+ configApp()
20
+ }
21
+
22
+ private fun configApp() {
23
+ Strada.config.jsonConverter = KotlinXJsonConverter()
17
24
  }
18
25
  }
@@ -1,8 +1,8 @@
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
@@ -10,6 +10,7 @@ import <%= package_name %>.features.web.WebHomeFragment
10
10
  import <%= package_name %>.features.web.WebModalFragment
11
11
  import <%= package_name %>.util.CURRENT_URL
12
12
  import dev.hotwire.turbo.session.TurboSessionNavHostFragment
13
+ import <%= package_name %>.util.customUserAgent
13
14
  import kotlin.reflect.KClass
14
15
 
15
16
  @Suppress("unused")
@@ -29,10 +30,9 @@ class MainSessionNavHostFragment : TurboSessionNavHostFragment() {
29
30
 
30
31
  override fun onSessionCreated() {
31
32
  super.onSessionCreated()
32
- session.webView.settings.userAgentString = customUserAgent(session.webView)
33
- }
33
+ session.webView.settings.userAgentString = session.webView.customUserAgent
34
34
 
35
- private fun customUserAgent(webView: WebView): String {
36
- return "Turbo Native Android ${webView.settings.userAgentString}"
35
+ // Initialize Strada bridge with new WebView instance
36
+ Bridge.initialize(session.webView)
37
37
  }
38
38
  }
@@ -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$", "/signin$"], "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
+ }
@@ -6,17 +6,20 @@ import androidx.browser.customtabs.CustomTabsIntent
6
6
  import androidx.browser.customtabs.CustomTabsIntent.SHARE_STATE_ON
7
7
  import androidx.navigation.NavOptions
8
8
  import androidx.navigation.navOptions
9
+ import com.google.android.material.snackbar.Snackbar
10
+ import dev.hotwire.strada.BridgeDestination
9
11
  import dev.hotwire.turbo.config.TurboPathConfigurationProperties
10
12
  import dev.hotwire.turbo.config.context
11
13
  import <%= package_name %>.R
12
14
  import <%= package_name %>.util.BASE_URL
13
15
  import dev.hotwire.turbo.nav.TurboNavDestination
16
+ import dev.hotwire.turbo.nav.TurboNavPresentationContext.DEFAULT
14
17
  import dev.hotwire.turbo.nav.TurboNavPresentationContext.MODAL
15
18
 
16
- interface NavDestination : TurboNavDestination {
19
+ interface NavDestination : TurboNavDestination, BridgeDestination {
17
20
  override fun shouldNavigateTo(newLocation: String): Boolean {
18
21
  return when (isNavigable(newLocation)) {
19
- true -> true
22
+ true -> { displayNoticeMessage(newLocation); true }
20
23
  else -> { launchCustomTab(newLocation); false }
21
24
  }
22
25
  }
@@ -31,10 +34,22 @@ interface NavDestination : TurboNavDestination {
31
34
  }
32
35
  }
33
36
 
37
+ override fun bridgeWebViewIsReady(): Boolean {
38
+ return session.isReady
39
+ }
40
+
34
41
  private fun isNavigable(location: String): Boolean {
35
42
  return location.startsWith(BASE_URL)
36
43
  }
37
44
 
45
+ private fun displayNoticeMessage(location: String) {
46
+ var message = Uri.parse(location).getQueryParameter("notice")
47
+
48
+ if (pathProperties.context == DEFAULT && message != null) {
49
+ Snackbar.make(fragment.requireView(), message, Snackbar.LENGTH_SHORT).show()
50
+ }
51
+ }
52
+
38
53
  private fun launchCustomTab(location: String) {
39
54
  val context = fragment.context ?: return
40
55
  val color = context.getColor(R.color.white)
@@ -1,13 +1,51 @@
1
1
  package <%= package_name %>.features.web
2
2
 
3
+ import android.os.Bundle
3
4
  import android.view.View
5
+ import dev.hotwire.strada.BridgeDelegate
4
6
  import <%= package_name %>.base.NavDestination
5
7
  import dev.hotwire.turbo.fragments.TurboWebFragment
6
8
  import dev.hotwire.turbo.nav.TurboNavGraphDestination
7
9
  import <%= package_name %>.R
10
+ import <%= package_name %>.strada.bridgeComponentFactories
11
+ import dev.hotwire.turbo.views.TurboWebView
8
12
 
9
13
  @TurboNavGraphDestination(uri = "turbo://fragment/web")
10
14
  open class WebFragment : TurboWebFragment(), NavDestination {
15
+ private val bridgeDelegate by lazy {
16
+ BridgeDelegate(
17
+ location = location,
18
+ destination = this,
19
+ componentFactories = bridgeComponentFactories
20
+ )
21
+ }
22
+
23
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
24
+ super.onViewCreated(view, savedInstanceState)
25
+ viewLifecycleOwner.lifecycle.addObserver(bridgeDelegate)
26
+ }
27
+
28
+ override fun onDestroyView() {
29
+ super.onDestroyView()
30
+ viewLifecycleOwner.lifecycle.removeObserver(bridgeDelegate)
31
+ }
32
+
33
+ override fun onColdBootPageStarted(location: String) {
34
+ bridgeDelegate.onColdBootPageStarted()
35
+ }
36
+
37
+ override fun onColdBootPageCompleted(location: String) {
38
+ bridgeDelegate.onColdBootPageCompleted()
39
+ }
40
+
41
+ override fun onWebViewAttached(webView: TurboWebView) {
42
+ bridgeDelegate.onWebViewAttached(webView)
43
+ }
44
+
45
+ override fun onWebViewDetached(webView: TurboWebView) {
46
+ bridgeDelegate.onWebViewDetached()
47
+ }
48
+
11
49
  override fun createErrorView(statusCode: Int): View {
12
50
  return layoutInflater.inflate(R.layout.error_web, null)
13
51
  }
@@ -3,7 +3,7 @@ package <%= package_name %>.features.web
3
3
  import android.os.Bundle
4
4
  import android.view.View
5
5
  import dev.hotwire.turbo.nav.TurboNavGraphDestination
6
- import <%= package_name %>.R
6
+ import <%= package_name %>.util.displayBackButtonAsCloseIcon
7
7
 
8
8
  @TurboNavGraphDestination(uri = "turbo://fragment/web/modal")
9
9
  class WebModalFragment : WebFragment() {
@@ -12,6 +12,6 @@ class WebModalFragment : WebFragment() {
12
12
  }
13
13
 
14
14
  private fun initToolbar() {
15
- toolbarForNavigation()?.navigationIcon = context?.getDrawable(R.drawable.ic_close)
15
+ toolbarForNavigation()?.displayBackButtonAsCloseIcon()
16
16
  }
17
17
  }