turbo-native-initializer 0.0.10 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
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
  }