turbo-native-initializer 0.0.11 → 0.0.13
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/features/web/WebFragment.kt.tt +5 -0
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/BridgeComponentFactories.kt.tt +1 -0
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/MenuComponent.kt.tt +78 -0
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/MenuComponentAdapter.kt.tt +57 -0
- data/lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/NavButtonComponent.kt.tt +14 -21
- data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/menu_component_adapter_row.xml +22 -0
- data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/menu_component_bottom_sheet.xml +33 -0
- data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/menu/web.xml +9 -0
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/features/web/WebFragment.kt.tt +5 -0
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/BridgeComponentFactories.kt.tt +1 -0
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/MenuComponent.kt.tt +78 -0
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/MenuComponentAdapter.kt.tt +57 -0
- data/lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/NavButtonComponent.kt.tt +14 -21
- data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/menu_component_adapter_row.xml +22 -0
- data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/menu_component_bottom_sheet.xml +33 -0
- data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/menu/web.xml +9 -0
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Controllers/UIViewController+Toast.swift +28 -8
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/BridgeComponent+App.swift +6 -1
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/MenuComponent.swift +77 -0
- data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject.xcodeproj/project.pbxproj.tt +4 -0
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Controllers/UIViewController+Toast.swift +28 -8
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/BridgeComponent+App.swift +6 -1
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/MenuComponent.swift +77 -0
- data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject.xcodeproj/project.pbxproj.tt +4 -0
- data/lib/turbo_native_initializer/version.rb +1 -1
- metadata +14 -4
- data/lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/nav_button_component.xml +0 -14
- data/lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/nav_button_component.xml +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f6134975dfb64645d0fe32a2dfcaa61efe0719458540d0e294cffc5547646397
|
4
|
+
data.tar.gz: df7dada5dc6d57ff85e58e2439c86a9e06cc0e4e89fe8a5547509da483493906
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2135c55c9a059bba1907bd90f776e352968d74af49cdc8173d8b6f1d92ecbf5b09e566d3626e02d5842a509fb1dea7b3d7dde0d847046da4cb9740186f5e04b4
|
7
|
+
data.tar.gz: 4c648e53161c6ab00f22787b3dc059cf06d8f259a9eabdd8aad0617e33d56ccac29ca6556f4eaa31fd8eb93fbbf50ad15d2d18e5b9f5fe3d1875369679bae5bc
|
data/README.md
CHANGED
@@ -12,7 +12,7 @@ A turbo native project generator for iOS and Android.
|
|
12
12
|
- Added `visitable` property in order to avoid visits. (iOS)
|
13
13
|
- Added support for tab navigation. (iOS/Android)
|
14
14
|
- Added support for flash messages. (iOS/Android)
|
15
|
-
-
|
15
|
+
- Integrated with the gem [strada-rails](https://github.com/lazaronixon/strada-rails).
|
16
16
|
|
17
17
|
## Installation
|
18
18
|
|
@@ -25,6 +25,7 @@ open class WebFragment : TurboWebFragment(), NavDestination {
|
|
25
25
|
|
26
26
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
27
27
|
super.onViewCreated(view, savedInstanceState)
|
28
|
+
setupMenu()
|
28
29
|
viewLifecycleOwner.lifecycle.addObserver(bridgeDelegate)
|
29
30
|
}
|
30
31
|
|
@@ -59,4 +60,8 @@ open class WebFragment : TurboWebFragment(), NavDestination {
|
|
59
60
|
override fun createErrorView(statusCode: Int): View {
|
60
61
|
return layoutInflater.inflate(R.layout.error_web, null)
|
61
62
|
}
|
63
|
+
|
64
|
+
private fun setupMenu() {
|
65
|
+
toolbarForNavigation()?.inflateMenu(R.menu.web)
|
66
|
+
}
|
62
67
|
}
|
@@ -6,4 +6,5 @@ val bridgeComponentFactories = listOf(
|
|
6
6
|
BridgeComponentFactory("form", ::FormComponent),
|
7
7
|
BridgeComponentFactory("nav-button", ::NavButtonComponent),
|
8
8
|
BridgeComponentFactory("flash-message", ::FlashMessageComponent),
|
9
|
+
BridgeComponentFactory("menu", ::MenuComponent)
|
9
10
|
)
|
@@ -0,0 +1,78 @@
|
|
1
|
+
package <%= package_name %>.strada
|
2
|
+
|
3
|
+
import android.util.Log
|
4
|
+
import android.view.LayoutInflater
|
5
|
+
import androidx.fragment.app.Fragment
|
6
|
+
import androidx.recyclerview.widget.LinearLayoutManager
|
7
|
+
import com.google.android.material.bottomsheet.BottomSheetDialog
|
8
|
+
import dev.hotwire.strada.BridgeComponent
|
9
|
+
import dev.hotwire.strada.BridgeDelegate
|
10
|
+
import dev.hotwire.strada.Message
|
11
|
+
import <%= package_name %>.base.NavDestination
|
12
|
+
import <%= package_name %>.databinding.MenuComponentBottomSheetBinding
|
13
|
+
import kotlinx.serialization.Serializable
|
14
|
+
|
15
|
+
class MenuComponent(
|
16
|
+
name: String,
|
17
|
+
private val delegate: BridgeDelegate<NavDestination>
|
18
|
+
) : BridgeComponent<NavDestination>(name, delegate) {
|
19
|
+
|
20
|
+
private val fragment: Fragment
|
21
|
+
get() = delegate.destination.fragment
|
22
|
+
|
23
|
+
override fun onReceive(message: Message) {
|
24
|
+
if (message.event == "display") {
|
25
|
+
handleDisplayEvent(message)
|
26
|
+
} else {
|
27
|
+
Log.w("TurboNative", "Unknown event for message: $message")
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
private fun handleDisplayEvent(message: Message) {
|
32
|
+
val data = message.data<MessageData>() ?: return
|
33
|
+
showBottomSheet(data.title, data.items)
|
34
|
+
}
|
35
|
+
|
36
|
+
private fun showBottomSheet(title: String, items: List<Item>) {
|
37
|
+
val view = fragment.view?.rootView ?: return
|
38
|
+
val inflater = LayoutInflater.from(view.context)
|
39
|
+
val bottomSheet = BottomSheetDialog(view.context)
|
40
|
+
val binding = MenuComponentBottomSheetBinding.inflate(inflater)
|
41
|
+
|
42
|
+
binding.toolbar.title = title
|
43
|
+
binding.recyclerView.layoutManager = LinearLayoutManager(view.context)
|
44
|
+
binding.recyclerView.adapter = MenuComponentAdapter().apply {
|
45
|
+
setData(items)
|
46
|
+
setListener {
|
47
|
+
bottomSheet.dismiss()
|
48
|
+
onItemSelected(it)
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
bottomSheet.apply {
|
53
|
+
setContentView(binding.root)
|
54
|
+
show()
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
private fun onItemSelected(item: Item) {
|
59
|
+
replyTo("display", SelectionMessageData(item.index))
|
60
|
+
}
|
61
|
+
|
62
|
+
@Serializable
|
63
|
+
data class MessageData(
|
64
|
+
val title: String,
|
65
|
+
val items: List<Item>
|
66
|
+
)
|
67
|
+
|
68
|
+
@Serializable
|
69
|
+
data class Item(
|
70
|
+
val title: String,
|
71
|
+
val index: Int
|
72
|
+
)
|
73
|
+
|
74
|
+
@Serializable
|
75
|
+
data class SelectionMessageData(
|
76
|
+
val selectedIndex: Int
|
77
|
+
)
|
78
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
package <%= package_name %>.strada
|
2
|
+
|
3
|
+
import android.annotation.SuppressLint
|
4
|
+
import android.view.LayoutInflater
|
5
|
+
import android.view.View
|
6
|
+
import android.view.ViewGroup
|
7
|
+
import androidx.recyclerview.widget.RecyclerView
|
8
|
+
import com.google.android.material.textview.MaterialTextView
|
9
|
+
import <%= package_name %>.R
|
10
|
+
|
11
|
+
class MenuComponentAdapter : RecyclerView.Adapter<MenuComponentAdapter.ViewHolder>() {
|
12
|
+
private val type = R.layout.menu_component_adapter_row
|
13
|
+
private var action: ((MenuComponent.Item) -> Unit)? = null
|
14
|
+
|
15
|
+
private var items = emptyList<MenuComponent.Item>()
|
16
|
+
@SuppressLint("NotifyDataSetChanged")
|
17
|
+
set(value) {
|
18
|
+
field = value
|
19
|
+
notifyDataSetChanged()
|
20
|
+
}
|
21
|
+
|
22
|
+
fun setData(items: List<MenuComponent.Item>) {
|
23
|
+
this.items = items
|
24
|
+
}
|
25
|
+
|
26
|
+
fun setListener(action: (item: MenuComponent.Item) -> Unit) {
|
27
|
+
this.action = action
|
28
|
+
}
|
29
|
+
|
30
|
+
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
31
|
+
holder.bind(items[position])
|
32
|
+
}
|
33
|
+
|
34
|
+
override fun getItemCount(): Int {
|
35
|
+
return items.count()
|
36
|
+
}
|
37
|
+
|
38
|
+
override fun getItemViewType(position: Int): Int {
|
39
|
+
return type
|
40
|
+
}
|
41
|
+
|
42
|
+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
43
|
+
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
44
|
+
return ViewHolder(view)
|
45
|
+
}
|
46
|
+
|
47
|
+
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
48
|
+
private val textView: MaterialTextView = view.findViewById(R.id.title)
|
49
|
+
|
50
|
+
fun bind(item: MenuComponent.Item) {
|
51
|
+
textView.text = item.title
|
52
|
+
itemView.setOnClickListener {
|
53
|
+
action?.invoke(item)
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
@@ -1,9 +1,6 @@
|
|
1
1
|
package <%= package_name %>.strada
|
2
2
|
|
3
3
|
import android.util.Log
|
4
|
-
import android.view.LayoutInflater
|
5
|
-
import android.view.Menu
|
6
|
-
import android.view.MenuItem
|
7
4
|
import androidx.appcompat.widget.Toolbar
|
8
5
|
import androidx.fragment.app.Fragment
|
9
6
|
import dev.hotwire.strada.BridgeComponent
|
@@ -11,7 +8,6 @@ import dev.hotwire.strada.BridgeDelegate
|
|
11
8
|
import dev.hotwire.strada.Message
|
12
9
|
import <%= package_name %>.R
|
13
10
|
import <%= package_name %>.base.NavDestination
|
14
|
-
import <%= package_name %>.databinding.NavButtonComponentBinding
|
15
11
|
import kotlinx.serialization.Serializable
|
16
12
|
|
17
13
|
class NavButtonComponent(
|
@@ -19,8 +15,6 @@ class NavButtonComponent(
|
|
19
15
|
private val delegate: BridgeDelegate<NavDestination>
|
20
16
|
) : BridgeComponent<NavDestination>(name, delegate) {
|
21
17
|
|
22
|
-
private val navButtonItemId = 20
|
23
|
-
private var navButtonMenuItem: MenuItem? = null
|
24
18
|
private val fragment: Fragment
|
25
19
|
get() = delegate.destination.fragment
|
26
20
|
private val toolbar: Toolbar?
|
@@ -40,27 +34,26 @@ class NavButtonComponent(
|
|
40
34
|
}
|
41
35
|
|
42
36
|
private fun showToolbarButton(data: MessageData) {
|
43
|
-
val
|
44
|
-
val inflater = LayoutInflater.from(fragment.requireContext())
|
45
|
-
val binding = NavButtonComponentBinding.inflate(inflater)
|
46
|
-
val order = 999 // Show as the right-most button
|
37
|
+
val toolbar = toolbar ?: return
|
47
38
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
performAction()
|
52
|
-
}
|
39
|
+
toolbar.menu.findItem(R.id.nav_button)?.apply {
|
40
|
+
isVisible = true
|
41
|
+
title = data.title
|
53
42
|
}
|
54
43
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
44
|
+
toolbar.setOnMenuItemClickListener {
|
45
|
+
when (it.itemId) {
|
46
|
+
R.id.nav_button -> {
|
47
|
+
performClick()
|
48
|
+
true
|
49
|
+
}
|
50
|
+
else -> false
|
51
|
+
}
|
59
52
|
}
|
60
53
|
}
|
61
54
|
|
62
|
-
private fun
|
63
|
-
|
55
|
+
private fun performClick() {
|
56
|
+
replyTo("connect")
|
64
57
|
}
|
65
58
|
|
66
59
|
@Serializable
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<androidx.constraintlayout.widget.ConstraintLayout
|
3
|
+
xmlns:android="http://schemas.android.com/apk/res/android"
|
4
|
+
xmlns:app="http://schemas.android.com/apk/res-auto"
|
5
|
+
android:layout_width="match_parent"
|
6
|
+
android:layout_height="wrap_content"
|
7
|
+
android:background="?selectableItemBackground">
|
8
|
+
|
9
|
+
<com.google.android.material.textview.MaterialTextView
|
10
|
+
style="@style/TextAppearance.AppCompat.Menu"
|
11
|
+
android:id="@+id/title"
|
12
|
+
android:layout_width="match_parent"
|
13
|
+
android:layout_height="wrap_content"
|
14
|
+
android:minHeight="48dp"
|
15
|
+
android:paddingLeft="16dp"
|
16
|
+
android:paddingTop="8dp"
|
17
|
+
android:paddingRight="16dp"
|
18
|
+
android:paddingBottom="8dp"
|
19
|
+
android:textSize="18sp"
|
20
|
+
app:layout_constraintTop_toTopOf="parent" />
|
21
|
+
|
22
|
+
</androidx.constraintlayout.widget.ConstraintLayout>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<androidx.constraintlayout.widget.ConstraintLayout
|
3
|
+
xmlns:android="http://schemas.android.com/apk/res/android"
|
4
|
+
xmlns:app="http://schemas.android.com/apk/res-auto"
|
5
|
+
android:layout_width="match_parent"
|
6
|
+
android:layout_height="match_parent"
|
7
|
+
android:background="@color/transparent">
|
8
|
+
|
9
|
+
<com.google.android.material.appbar.AppBarLayout
|
10
|
+
android:id="@+id/app_bar"
|
11
|
+
android:layout_width="match_parent"
|
12
|
+
android:layout_height="wrap_content"
|
13
|
+
android:background="@color/transparent"
|
14
|
+
android:stateListAnimator="@null"
|
15
|
+
app:layout_constraintEnd_toEndOf="parent"
|
16
|
+
app:layout_constraintStart_toStartOf="parent"
|
17
|
+
app:layout_constraintTop_toTopOf="parent">
|
18
|
+
|
19
|
+
<com.google.android.material.appbar.MaterialToolbar
|
20
|
+
android:id="@+id/toolbar"
|
21
|
+
android:layout_width="match_parent"
|
22
|
+
android:layout_height="wrap_content" />
|
23
|
+
|
24
|
+
</com.google.android.material.appbar.AppBarLayout>
|
25
|
+
|
26
|
+
<androidx.recyclerview.widget.RecyclerView
|
27
|
+
android:id="@+id/recycler_view"
|
28
|
+
android:layout_width="match_parent"
|
29
|
+
android:layout_height="wrap_content"
|
30
|
+
app:layout_constraintBottom_toBottomOf="parent"
|
31
|
+
app:layout_constraintTop_toBottomOf="@+id/app_bar" />
|
32
|
+
|
33
|
+
</androidx.constraintlayout.widget.ConstraintLayout>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
3
|
+
xmlns:app="http://schemas.android.com/apk/res-auto">
|
4
|
+
<item
|
5
|
+
android:id="@+id/nav_button"
|
6
|
+
android:orderInCategory="999"
|
7
|
+
android:visible="false"
|
8
|
+
app:showAsAction="always" />
|
9
|
+
</menu>
|
@@ -22,6 +22,7 @@ open class WebFragment : TurboWebFragment(), NavDestination {
|
|
22
22
|
|
23
23
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
24
24
|
super.onViewCreated(view, savedInstanceState)
|
25
|
+
setupMenu()
|
25
26
|
viewLifecycleOwner.lifecycle.addObserver(bridgeDelegate)
|
26
27
|
}
|
27
28
|
|
@@ -49,4 +50,8 @@ open class WebFragment : TurboWebFragment(), NavDestination {
|
|
49
50
|
override fun createErrorView(statusCode: Int): View {
|
50
51
|
return layoutInflater.inflate(R.layout.error_web, null)
|
51
52
|
}
|
53
|
+
|
54
|
+
private fun setupMenu() {
|
55
|
+
toolbarForNavigation()?.inflateMenu(R.menu.web)
|
56
|
+
}
|
52
57
|
}
|
@@ -6,4 +6,5 @@ val bridgeComponentFactories = listOf(
|
|
6
6
|
BridgeComponentFactory("form", ::FormComponent),
|
7
7
|
BridgeComponentFactory("nav-button", ::NavButtonComponent),
|
8
8
|
BridgeComponentFactory("flash-message", ::FlashMessageComponent),
|
9
|
+
BridgeComponentFactory("menu", ::MenuComponent)
|
9
10
|
)
|
@@ -0,0 +1,78 @@
|
|
1
|
+
package <%= package_name %>.strada
|
2
|
+
|
3
|
+
import android.util.Log
|
4
|
+
import android.view.LayoutInflater
|
5
|
+
import androidx.fragment.app.Fragment
|
6
|
+
import androidx.recyclerview.widget.LinearLayoutManager
|
7
|
+
import com.google.android.material.bottomsheet.BottomSheetDialog
|
8
|
+
import dev.hotwire.strada.BridgeComponent
|
9
|
+
import dev.hotwire.strada.BridgeDelegate
|
10
|
+
import dev.hotwire.strada.Message
|
11
|
+
import <%= package_name %>.base.NavDestination
|
12
|
+
import <%= package_name %>.databinding.MenuComponentBottomSheetBinding
|
13
|
+
import kotlinx.serialization.Serializable
|
14
|
+
|
15
|
+
class MenuComponent(
|
16
|
+
name: String,
|
17
|
+
private val delegate: BridgeDelegate<NavDestination>
|
18
|
+
) : BridgeComponent<NavDestination>(name, delegate) {
|
19
|
+
|
20
|
+
private val fragment: Fragment
|
21
|
+
get() = delegate.destination.fragment
|
22
|
+
|
23
|
+
override fun onReceive(message: Message) {
|
24
|
+
if (message.event == "display") {
|
25
|
+
handleDisplayEvent(message)
|
26
|
+
} else {
|
27
|
+
Log.w("TurboNative", "Unknown event for message: $message")
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
private fun handleDisplayEvent(message: Message) {
|
32
|
+
val data = message.data<MessageData>() ?: return
|
33
|
+
showBottomSheet(data.title, data.items)
|
34
|
+
}
|
35
|
+
|
36
|
+
private fun showBottomSheet(title: String, items: List<Item>) {
|
37
|
+
val view = fragment.view?.rootView ?: return
|
38
|
+
val inflater = LayoutInflater.from(view.context)
|
39
|
+
val bottomSheet = BottomSheetDialog(view.context)
|
40
|
+
val binding = MenuComponentBottomSheetBinding.inflate(inflater)
|
41
|
+
|
42
|
+
binding.toolbar.title = title
|
43
|
+
binding.recyclerView.layoutManager = LinearLayoutManager(view.context)
|
44
|
+
binding.recyclerView.adapter = MenuComponentAdapter().apply {
|
45
|
+
setData(items)
|
46
|
+
setListener {
|
47
|
+
bottomSheet.dismiss()
|
48
|
+
onItemSelected(it)
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
bottomSheet.apply {
|
53
|
+
setContentView(binding.root)
|
54
|
+
show()
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
private fun onItemSelected(item: Item) {
|
59
|
+
replyTo("display", SelectionMessageData(item.index))
|
60
|
+
}
|
61
|
+
|
62
|
+
@Serializable
|
63
|
+
data class MessageData(
|
64
|
+
val title: String,
|
65
|
+
val items: List<Item>
|
66
|
+
)
|
67
|
+
|
68
|
+
@Serializable
|
69
|
+
data class Item(
|
70
|
+
val title: String,
|
71
|
+
val index: Int
|
72
|
+
)
|
73
|
+
|
74
|
+
@Serializable
|
75
|
+
data class SelectionMessageData(
|
76
|
+
val selectedIndex: Int
|
77
|
+
)
|
78
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
package <%= package_name %>.strada
|
2
|
+
|
3
|
+
import android.annotation.SuppressLint
|
4
|
+
import android.view.LayoutInflater
|
5
|
+
import android.view.View
|
6
|
+
import android.view.ViewGroup
|
7
|
+
import androidx.recyclerview.widget.RecyclerView
|
8
|
+
import com.google.android.material.textview.MaterialTextView
|
9
|
+
import <%= package_name %>.R
|
10
|
+
|
11
|
+
class MenuComponentAdapter : RecyclerView.Adapter<MenuComponentAdapter.ViewHolder>() {
|
12
|
+
private val type = R.layout.menu_component_adapter_row
|
13
|
+
private var action: ((MenuComponent.Item) -> Unit)? = null
|
14
|
+
|
15
|
+
private var items = emptyList<MenuComponent.Item>()
|
16
|
+
@SuppressLint("NotifyDataSetChanged")
|
17
|
+
set(value) {
|
18
|
+
field = value
|
19
|
+
notifyDataSetChanged()
|
20
|
+
}
|
21
|
+
|
22
|
+
fun setData(items: List<MenuComponent.Item>) {
|
23
|
+
this.items = items
|
24
|
+
}
|
25
|
+
|
26
|
+
fun setListener(action: (item: MenuComponent.Item) -> Unit) {
|
27
|
+
this.action = action
|
28
|
+
}
|
29
|
+
|
30
|
+
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
31
|
+
holder.bind(items[position])
|
32
|
+
}
|
33
|
+
|
34
|
+
override fun getItemCount(): Int {
|
35
|
+
return items.count()
|
36
|
+
}
|
37
|
+
|
38
|
+
override fun getItemViewType(position: Int): Int {
|
39
|
+
return type
|
40
|
+
}
|
41
|
+
|
42
|
+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
43
|
+
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
44
|
+
return ViewHolder(view)
|
45
|
+
}
|
46
|
+
|
47
|
+
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
48
|
+
private val textView: MaterialTextView = view.findViewById(R.id.title)
|
49
|
+
|
50
|
+
fun bind(item: MenuComponent.Item) {
|
51
|
+
textView.text = item.title
|
52
|
+
itemView.setOnClickListener {
|
53
|
+
action?.invoke(item)
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
@@ -1,9 +1,6 @@
|
|
1
1
|
package <%= package_name %>.strada
|
2
2
|
|
3
3
|
import android.util.Log
|
4
|
-
import android.view.LayoutInflater
|
5
|
-
import android.view.Menu
|
6
|
-
import android.view.MenuItem
|
7
4
|
import androidx.appcompat.widget.Toolbar
|
8
5
|
import androidx.fragment.app.Fragment
|
9
6
|
import dev.hotwire.strada.BridgeComponent
|
@@ -11,7 +8,6 @@ import dev.hotwire.strada.BridgeDelegate
|
|
11
8
|
import dev.hotwire.strada.Message
|
12
9
|
import <%= package_name %>.R
|
13
10
|
import <%= package_name %>.base.NavDestination
|
14
|
-
import <%= package_name %>.databinding.NavButtonComponentBinding
|
15
11
|
import kotlinx.serialization.Serializable
|
16
12
|
|
17
13
|
class NavButtonComponent(
|
@@ -19,8 +15,6 @@ class NavButtonComponent(
|
|
19
15
|
private val delegate: BridgeDelegate<NavDestination>
|
20
16
|
) : BridgeComponent<NavDestination>(name, delegate) {
|
21
17
|
|
22
|
-
private val navButtonItemId = 20
|
23
|
-
private var navButtonMenuItem: MenuItem? = null
|
24
18
|
private val fragment: Fragment
|
25
19
|
get() = delegate.destination.fragment
|
26
20
|
private val toolbar: Toolbar?
|
@@ -40,27 +34,26 @@ class NavButtonComponent(
|
|
40
34
|
}
|
41
35
|
|
42
36
|
private fun showToolbarButton(data: MessageData) {
|
43
|
-
val
|
44
|
-
val inflater = LayoutInflater.from(fragment.requireContext())
|
45
|
-
val binding = NavButtonComponentBinding.inflate(inflater)
|
46
|
-
val order = 999 // Show as the right-most button
|
37
|
+
val toolbar = toolbar ?: return
|
47
38
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
performAction()
|
52
|
-
}
|
39
|
+
toolbar.menu.findItem(R.id.nav_button)?.apply {
|
40
|
+
isVisible = true
|
41
|
+
title = data.title
|
53
42
|
}
|
54
43
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
44
|
+
toolbar.setOnMenuItemClickListener {
|
45
|
+
when (it.itemId) {
|
46
|
+
R.id.nav_button -> {
|
47
|
+
performClick()
|
48
|
+
true
|
49
|
+
}
|
50
|
+
else -> false
|
51
|
+
}
|
59
52
|
}
|
60
53
|
}
|
61
54
|
|
62
|
-
private fun
|
63
|
-
|
55
|
+
private fun performClick() {
|
56
|
+
replyTo("connect")
|
64
57
|
}
|
65
58
|
|
66
59
|
@Serializable
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<androidx.constraintlayout.widget.ConstraintLayout
|
3
|
+
xmlns:android="http://schemas.android.com/apk/res/android"
|
4
|
+
xmlns:app="http://schemas.android.com/apk/res-auto"
|
5
|
+
android:layout_width="match_parent"
|
6
|
+
android:layout_height="wrap_content"
|
7
|
+
android:background="?selectableItemBackground">
|
8
|
+
|
9
|
+
<com.google.android.material.textview.MaterialTextView
|
10
|
+
style="@style/TextAppearance.AppCompat.Menu"
|
11
|
+
android:id="@+id/title"
|
12
|
+
android:layout_width="match_parent"
|
13
|
+
android:layout_height="wrap_content"
|
14
|
+
android:minHeight="48dp"
|
15
|
+
android:paddingLeft="16dp"
|
16
|
+
android:paddingTop="8dp"
|
17
|
+
android:paddingRight="16dp"
|
18
|
+
android:paddingBottom="8dp"
|
19
|
+
android:textSize="18sp"
|
20
|
+
app:layout_constraintTop_toTopOf="parent" />
|
21
|
+
|
22
|
+
</androidx.constraintlayout.widget.ConstraintLayout>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<androidx.constraintlayout.widget.ConstraintLayout
|
3
|
+
xmlns:android="http://schemas.android.com/apk/res/android"
|
4
|
+
xmlns:app="http://schemas.android.com/apk/res-auto"
|
5
|
+
android:layout_width="match_parent"
|
6
|
+
android:layout_height="match_parent"
|
7
|
+
android:background="@color/transparent">
|
8
|
+
|
9
|
+
<com.google.android.material.appbar.AppBarLayout
|
10
|
+
android:id="@+id/app_bar"
|
11
|
+
android:layout_width="match_parent"
|
12
|
+
android:layout_height="wrap_content"
|
13
|
+
android:background="@color/transparent"
|
14
|
+
android:stateListAnimator="@null"
|
15
|
+
app:layout_constraintEnd_toEndOf="parent"
|
16
|
+
app:layout_constraintStart_toStartOf="parent"
|
17
|
+
app:layout_constraintTop_toTopOf="parent">
|
18
|
+
|
19
|
+
<com.google.android.material.appbar.MaterialToolbar
|
20
|
+
android:id="@+id/toolbar"
|
21
|
+
android:layout_width="match_parent"
|
22
|
+
android:layout_height="wrap_content" />
|
23
|
+
|
24
|
+
</com.google.android.material.appbar.AppBarLayout>
|
25
|
+
|
26
|
+
<androidx.recyclerview.widget.RecyclerView
|
27
|
+
android:id="@+id/recycler_view"
|
28
|
+
android:layout_width="match_parent"
|
29
|
+
android:layout_height="wrap_content"
|
30
|
+
app:layout_constraintBottom_toBottomOf="parent"
|
31
|
+
app:layout_constraintTop_toBottomOf="@+id/app_bar" />
|
32
|
+
|
33
|
+
</androidx.constraintlayout.widget.ConstraintLayout>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
3
|
+
xmlns:app="http://schemas.android.com/apk/res-auto">
|
4
|
+
<item
|
5
|
+
android:id="@+id/nav_button"
|
6
|
+
android:orderInCategory="999"
|
7
|
+
android:visible="false"
|
8
|
+
app:showAsAction="always" />
|
9
|
+
</menu>
|
@@ -4,22 +4,44 @@ public extension UIViewController {
|
|
4
4
|
func presentToast(_ message: String) {
|
5
5
|
guard let root = view.window?.rootViewController else { return }
|
6
6
|
|
7
|
+
removeToastViews(from: root)
|
8
|
+
|
7
9
|
let toastView = ToastView(message: message)
|
8
10
|
toastView.translatesAutoresizingMaskIntoConstraints = false
|
9
11
|
|
10
12
|
root.view.addSubview(toastView)
|
11
13
|
|
12
14
|
NSLayoutConstraint.activate([
|
13
|
-
toastView.centerXAnchor.constraint(equalTo: root.view.centerXAnchor),
|
14
15
|
toastView.topAnchor.constraint(equalTo: root.view.safeAreaLayoutGuide.topAnchor),
|
15
|
-
toastView.
|
16
|
+
toastView.centerXAnchor.constraint(equalTo: root.view.centerXAnchor)
|
17
|
+
])
|
18
|
+
|
19
|
+
let widthConstraint = toastView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -10)
|
20
|
+
widthConstraint.priority = .defaultHigh
|
21
|
+
|
22
|
+
let maxWidthConstraint = toastView.widthAnchor.constraint(lessThanOrEqualToConstant: 600)
|
23
|
+
maxWidthConstraint.priority = .required
|
24
|
+
|
25
|
+
NSLayoutConstraint.activate([
|
26
|
+
widthConstraint, maxWidthConstraint
|
16
27
|
])
|
17
28
|
}
|
29
|
+
|
30
|
+
fileprivate func removeToastViews(from root: UIViewController) {
|
31
|
+
root.view.subviews.filter({ $0 is ToastView }).forEach({ toast in
|
32
|
+
toast.removeFromSuperview()
|
33
|
+
})
|
34
|
+
}
|
18
35
|
}
|
19
36
|
|
20
37
|
public class ToastView: UIView {
|
38
|
+
|
39
|
+
private var duration = 2.5
|
40
|
+
|
21
41
|
convenience init(message: String) {
|
22
42
|
self.init(frame: .zero)
|
43
|
+
|
44
|
+
self.alpha = .zero
|
23
45
|
self.backgroundColor = .black
|
24
46
|
self.layer.cornerRadius = 10
|
25
47
|
|
@@ -40,17 +62,15 @@ public class ToastView: UIView {
|
|
40
62
|
|
41
63
|
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dismiss)))
|
42
64
|
|
43
|
-
|
44
|
-
|
45
|
-
UIView.animate(withDuration: 0.5, delay: .zero, animations: {
|
65
|
+
UIView.animate(withDuration: 0.5, delay: .zero, options: .curveEaseIn, animations: {
|
46
66
|
self.alpha = 0.9
|
47
67
|
}, completion: { _ in
|
48
|
-
DispatchQueue.main.asyncAfter(deadline: .now() +
|
68
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + self.duration) { self.dismiss() }
|
49
69
|
})
|
50
70
|
}
|
51
71
|
|
52
|
-
@objc func dismiss() {
|
53
|
-
UIView.animate(withDuration: 0.5, delay: .zero, animations: {
|
72
|
+
@objc private func dismiss() {
|
73
|
+
UIView.animate(withDuration: 0.5, delay: .zero, options: .curveEaseOut, animations: {
|
54
74
|
self.alpha = .zero
|
55
75
|
}, completion: { _ in
|
56
76
|
self.removeFromSuperview()
|
@@ -3,6 +3,11 @@ import Strada
|
|
3
3
|
|
4
4
|
extension BridgeComponent {
|
5
5
|
static var allTypes: [BridgeComponent.Type] {
|
6
|
-
[
|
6
|
+
[
|
7
|
+
FormComponent.self,
|
8
|
+
NavButtonComponent.self,
|
9
|
+
MenuComponent.self,
|
10
|
+
FlashMessageComponent.self
|
11
|
+
]
|
7
12
|
}
|
8
13
|
}
|
data/lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/MenuComponent.swift
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
import Foundation
|
2
|
+
import Strada
|
3
|
+
import UIKit
|
4
|
+
|
5
|
+
final class MenuComponent: BridgeComponent {
|
6
|
+
override class var name: String { "menu" }
|
7
|
+
|
8
|
+
override func onReceive(message: Message) {
|
9
|
+
guard let event = Event(rawValue: message.event) else {
|
10
|
+
return
|
11
|
+
}
|
12
|
+
|
13
|
+
if event == .display {
|
14
|
+
handleDisplayEvent(message: message)
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
// MARK: Private
|
19
|
+
|
20
|
+
private var viewController: UIViewController? {
|
21
|
+
delegate.destination as? UIViewController
|
22
|
+
}
|
23
|
+
|
24
|
+
private func handleDisplayEvent(message: Message) {
|
25
|
+
guard let data: MessageData = message.data() else { return }
|
26
|
+
showAlertSheet(with: data.title, items: data.items)
|
27
|
+
}
|
28
|
+
|
29
|
+
private func showAlertSheet(with title: String, items: [Item]) {
|
30
|
+
let alertController = UIAlertController(title: title,
|
31
|
+
message: nil,
|
32
|
+
preferredStyle: .actionSheet)
|
33
|
+
|
34
|
+
for item in items {
|
35
|
+
let action = UIAlertAction(title: item.title, style: .default) {[weak self] _ in
|
36
|
+
self?.onItemSelected(item: item)
|
37
|
+
}
|
38
|
+
alertController.addAction(action)
|
39
|
+
}
|
40
|
+
|
41
|
+
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
|
42
|
+
alertController.addAction(cancelAction)
|
43
|
+
|
44
|
+
viewController?.present(alertController, animated: true)
|
45
|
+
}
|
46
|
+
|
47
|
+
private func onItemSelected(item: Item) {
|
48
|
+
reply(to: Event.display.rawValue,
|
49
|
+
with: SelectionMessageData(selectedIndex: item.index))
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
// MARK: Events
|
54
|
+
|
55
|
+
private extension MenuComponent {
|
56
|
+
enum Event: String {
|
57
|
+
case display
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
// MARK: Message data
|
62
|
+
|
63
|
+
private extension MenuComponent {
|
64
|
+
struct MessageData: Decodable {
|
65
|
+
let title: String
|
66
|
+
let items: [Item]
|
67
|
+
}
|
68
|
+
|
69
|
+
struct Item: Decodable {
|
70
|
+
let title: String
|
71
|
+
let index: Int
|
72
|
+
}
|
73
|
+
|
74
|
+
struct SelectionMessageData: Encodable {
|
75
|
+
let selectedIndex:Int
|
76
|
+
}
|
77
|
+
}
|
@@ -14,6 +14,7 @@
|
|
14
14
|
5D281BB42ABC0CB0001CE599 /* BridgeComponent+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D281BB32ABC0CB0001CE599 /* BridgeComponent+App.swift */; };
|
15
15
|
5D281BB62ABC0CB9001CE599 /* FormComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D281BB52ABC0CB9001CE599 /* FormComponent.swift */; };
|
16
16
|
5D91C5402AC538960046D872 /* FlashMessageComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D91C53F2AC538960046D872 /* FlashMessageComponent.swift */; };
|
17
|
+
5DA9C92F2AC7E38100FEA7E6 /* MenuComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA9C92E2AC7E38100FEA7E6 /* MenuComponent.swift */; };
|
17
18
|
5DAF71B72AC3FB53002D04FE /* UIViewController+Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DAF71B62AC3FB53002D04FE /* UIViewController+Toast.swift */; };
|
18
19
|
5DCC50D42A959DF900B529A0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCC50D32A959DF900B529A0 /* AppDelegate.swift */; };
|
19
20
|
5DCC50D62A959DF900B529A0 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCC50D52A959DF900B529A0 /* SceneDelegate.swift */; };
|
@@ -35,6 +36,7 @@
|
|
35
36
|
5D281BB32ABC0CB0001CE599 /* BridgeComponent+App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BridgeComponent+App.swift"; sourceTree = "<group>"; };
|
36
37
|
5D281BB52ABC0CB9001CE599 /* FormComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormComponent.swift; sourceTree = "<group>"; };
|
37
38
|
5D91C53F2AC538960046D872 /* FlashMessageComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlashMessageComponent.swift; sourceTree = "<group>"; };
|
39
|
+
5DA9C92E2AC7E38100FEA7E6 /* MenuComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuComponent.swift; sourceTree = "<group>"; };
|
38
40
|
5DAF71B62AC3FB53002D04FE /* UIViewController+Toast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Toast.swift"; sourceTree = "<group>"; };
|
39
41
|
5DCC50D02A959DF900B529A0 /* <%= name %>.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = <%= name %>.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
40
42
|
5DCC50D32A959DF900B529A0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
@@ -70,6 +72,7 @@
|
|
70
72
|
5D281BB52ABC0CB9001CE599 /* FormComponent.swift */,
|
71
73
|
5D1F2EC02ABEB12300B2819A /* NavButtonComponent.swift */,
|
72
74
|
5D91C53F2AC538960046D872 /* FlashMessageComponent.swift */,
|
75
|
+
5DA9C92E2AC7E38100FEA7E6 /* MenuComponent.swift */,
|
73
76
|
);
|
74
77
|
path = Strada;
|
75
78
|
sourceTree = "<group>";
|
@@ -224,6 +227,7 @@
|
|
224
227
|
isa = PBXSourcesBuildPhase;
|
225
228
|
buildActionMask = 2147483647;
|
226
229
|
files = (
|
230
|
+
5DA9C92F2AC7E38100FEA7E6 /* MenuComponent.swift in Sources */,
|
227
231
|
5DCC50D82A959DF900B529A0 /* TurboWebViewController.swift in Sources */,
|
228
232
|
5DDD58812AA9A8BE00FAC961 /* NumbersViewController.swift in Sources */,
|
229
233
|
5D91C5402AC538960046D872 /* FlashMessageComponent.swift in Sources */,
|
@@ -4,22 +4,44 @@ public extension UIViewController {
|
|
4
4
|
func presentToast(_ message: String) {
|
5
5
|
guard let root = view.window?.rootViewController else { return }
|
6
6
|
|
7
|
+
removeToastViews(from: root)
|
8
|
+
|
7
9
|
let toastView = ToastView(message: message)
|
8
10
|
toastView.translatesAutoresizingMaskIntoConstraints = false
|
9
11
|
|
10
12
|
root.view.addSubview(toastView)
|
11
13
|
|
12
14
|
NSLayoutConstraint.activate([
|
13
|
-
toastView.centerXAnchor.constraint(equalTo: root.view.centerXAnchor),
|
14
15
|
toastView.topAnchor.constraint(equalTo: root.view.safeAreaLayoutGuide.topAnchor),
|
15
|
-
toastView.
|
16
|
+
toastView.centerXAnchor.constraint(equalTo: root.view.centerXAnchor)
|
17
|
+
])
|
18
|
+
|
19
|
+
let widthConstraint = toastView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -10)
|
20
|
+
widthConstraint.priority = .defaultHigh
|
21
|
+
|
22
|
+
let maxWidthConstraint = toastView.widthAnchor.constraint(lessThanOrEqualToConstant: 600)
|
23
|
+
maxWidthConstraint.priority = .required
|
24
|
+
|
25
|
+
NSLayoutConstraint.activate([
|
26
|
+
widthConstraint, maxWidthConstraint
|
16
27
|
])
|
17
28
|
}
|
29
|
+
|
30
|
+
fileprivate func removeToastViews(from root: UIViewController) {
|
31
|
+
root.view.subviews.filter({ $0 is ToastView }).forEach({ toast in
|
32
|
+
toast.removeFromSuperview()
|
33
|
+
})
|
34
|
+
}
|
18
35
|
}
|
19
36
|
|
20
37
|
public class ToastView: UIView {
|
38
|
+
|
39
|
+
private var duration = 2.5
|
40
|
+
|
21
41
|
convenience init(message: String) {
|
22
42
|
self.init(frame: .zero)
|
43
|
+
|
44
|
+
self.alpha = .zero
|
23
45
|
self.backgroundColor = .black
|
24
46
|
self.layer.cornerRadius = 10
|
25
47
|
|
@@ -40,17 +62,15 @@ public class ToastView: UIView {
|
|
40
62
|
|
41
63
|
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dismiss)))
|
42
64
|
|
43
|
-
|
44
|
-
|
45
|
-
UIView.animate(withDuration: 0.5, delay: .zero, animations: {
|
65
|
+
UIView.animate(withDuration: 0.5, delay: .zero, options: .curveEaseIn, animations: {
|
46
66
|
self.alpha = 0.9
|
47
67
|
}, completion: { _ in
|
48
|
-
DispatchQueue.main.asyncAfter(deadline: .now() +
|
68
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + self.duration) { self.dismiss() }
|
49
69
|
})
|
50
70
|
}
|
51
71
|
|
52
|
-
@objc func dismiss() {
|
53
|
-
UIView.animate(withDuration: 0.5, delay: .zero, animations: {
|
72
|
+
@objc private func dismiss() {
|
73
|
+
UIView.animate(withDuration: 0.5, delay: .zero, options: .curveEaseOut, animations: {
|
54
74
|
self.alpha = .zero
|
55
75
|
}, completion: { _ in
|
56
76
|
self.removeFromSuperview()
|
@@ -3,6 +3,11 @@ import Strada
|
|
3
3
|
|
4
4
|
extension BridgeComponent {
|
5
5
|
static var allTypes: [BridgeComponent.Type] {
|
6
|
-
[
|
6
|
+
[
|
7
|
+
FormComponent.self,
|
8
|
+
NavButtonComponent.self,
|
9
|
+
MenuComponent.self,
|
10
|
+
FlashMessageComponent.self
|
11
|
+
]
|
7
12
|
}
|
8
13
|
}
|
data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/MenuComponent.swift
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
import Foundation
|
2
|
+
import Strada
|
3
|
+
import UIKit
|
4
|
+
|
5
|
+
final class MenuComponent: BridgeComponent {
|
6
|
+
override class var name: String { "menu" }
|
7
|
+
|
8
|
+
override func onReceive(message: Message) {
|
9
|
+
guard let event = Event(rawValue: message.event) else {
|
10
|
+
return
|
11
|
+
}
|
12
|
+
|
13
|
+
if event == .display {
|
14
|
+
handleDisplayEvent(message: message)
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
// MARK: Private
|
19
|
+
|
20
|
+
private var viewController: UIViewController? {
|
21
|
+
delegate.destination as? UIViewController
|
22
|
+
}
|
23
|
+
|
24
|
+
private func handleDisplayEvent(message: Message) {
|
25
|
+
guard let data: MessageData = message.data() else { return }
|
26
|
+
showAlertSheet(with: data.title, items: data.items)
|
27
|
+
}
|
28
|
+
|
29
|
+
private func showAlertSheet(with title: String, items: [Item]) {
|
30
|
+
let alertController = UIAlertController(title: title,
|
31
|
+
message: nil,
|
32
|
+
preferredStyle: .actionSheet)
|
33
|
+
|
34
|
+
for item in items {
|
35
|
+
let action = UIAlertAction(title: item.title, style: .default) {[weak self] _ in
|
36
|
+
self?.onItemSelected(item: item)
|
37
|
+
}
|
38
|
+
alertController.addAction(action)
|
39
|
+
}
|
40
|
+
|
41
|
+
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
|
42
|
+
alertController.addAction(cancelAction)
|
43
|
+
|
44
|
+
viewController?.present(alertController, animated: true)
|
45
|
+
}
|
46
|
+
|
47
|
+
private func onItemSelected(item: Item) {
|
48
|
+
reply(to: Event.display.rawValue,
|
49
|
+
with: SelectionMessageData(selectedIndex: item.index))
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
// MARK: Events
|
54
|
+
|
55
|
+
private extension MenuComponent {
|
56
|
+
enum Event: String {
|
57
|
+
case display
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
// MARK: Message data
|
62
|
+
|
63
|
+
private extension MenuComponent {
|
64
|
+
struct MessageData: Decodable {
|
65
|
+
let title: String
|
66
|
+
let items: [Item]
|
67
|
+
}
|
68
|
+
|
69
|
+
struct Item: Decodable {
|
70
|
+
let title: String
|
71
|
+
let index: Int
|
72
|
+
}
|
73
|
+
|
74
|
+
struct SelectionMessageData: Encodable {
|
75
|
+
let selectedIndex:Int
|
76
|
+
}
|
77
|
+
}
|
data/lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject.xcodeproj/project.pbxproj.tt
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
5D8AB2FC2ABC117F00C6A82F /* BridgeComponent+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8AB2FA2ABC117F00C6A82F /* BridgeComponent+App.swift */; };
|
16
16
|
5D8AB2FE2ABC119900C6A82F /* WKWebViewConfiguration+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8AB2FD2ABC119900C6A82F /* WKWebViewConfiguration+App.swift */; };
|
17
17
|
5D8AB3012ABC127300C6A82F /* Strada in Frameworks */ = {isa = PBXBuildFile; productRef = 5D8AB3002ABC127300C6A82F /* Strada */; };
|
18
|
+
5DA9C9352AC874D200FEA7E6 /* MenuComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA9C9342AC874D200FEA7E6 /* MenuComponent.swift */; };
|
18
19
|
5DCC50D42A959DF900B529A0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCC50D32A959DF900B529A0 /* AppDelegate.swift */; };
|
19
20
|
5DCC50D62A959DF900B529A0 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCC50D52A959DF900B529A0 /* SceneDelegate.swift */; };
|
20
21
|
5DCC50D82A959DF900B529A0 /* TurboWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCC50D72A959DF900B529A0 /* TurboWebViewController.swift */; };
|
@@ -36,6 +37,7 @@
|
|
36
37
|
5D8AB2F92ABC117F00C6A82F /* FormComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormComponent.swift; sourceTree = "<group>"; };
|
37
38
|
5D8AB2FA2ABC117F00C6A82F /* BridgeComponent+App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BridgeComponent+App.swift"; sourceTree = "<group>"; };
|
38
39
|
5D8AB2FD2ABC119900C6A82F /* WKWebViewConfiguration+App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WKWebViewConfiguration+App.swift"; sourceTree = "<group>"; };
|
40
|
+
5DA9C9342AC874D200FEA7E6 /* MenuComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuComponent.swift; sourceTree = "<group>"; };
|
39
41
|
5DCC50D02A959DF900B529A0 /* <%= name %>.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = <%= name %>.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
40
42
|
5DCC50D32A959DF900B529A0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
41
43
|
5DCC50D52A959DF900B529A0 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
@@ -79,6 +81,7 @@
|
|
79
81
|
5D8AB2F92ABC117F00C6A82F /* FormComponent.swift */,
|
80
82
|
5D1F2EC02ABEB12300B2819A /* NavButtonComponent.swift */,
|
81
83
|
5D2381402AC53D8900FABA1F /* FlashMessageComponent.swift */,
|
84
|
+
5DA9C9342AC874D200FEA7E6 /* MenuComponent.swift */,
|
82
85
|
);
|
83
86
|
path = Strada;
|
84
87
|
sourceTree = "<group>";
|
@@ -224,6 +227,7 @@
|
|
224
227
|
isa = PBXSourcesBuildPhase;
|
225
228
|
buildActionMask = 2147483647;
|
226
229
|
files = (
|
230
|
+
5DA9C9352AC874D200FEA7E6 /* MenuComponent.swift in Sources */,
|
227
231
|
5DCC50D82A959DF900B529A0 /* TurboWebViewController.swift in Sources */,
|
228
232
|
5DDD58812AA9A8BE00FAC961 /* NumbersViewController.swift in Sources */,
|
229
233
|
5D2381412AC53D8900FABA1F /* FlashMessageComponent.swift in Sources */,
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turbo-native-initializer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nixon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-09-
|
11
|
+
date: 2023-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -52,6 +52,8 @@ files:
|
|
52
52
|
- lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/BridgeComponentFactories.kt.tt
|
53
53
|
- lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FlashMessageComponent.kt.tt
|
54
54
|
- lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FormComponent.kt.tt
|
55
|
+
- lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/MenuComponent.kt.tt
|
56
|
+
- lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/MenuComponentAdapter.kt.tt
|
55
57
|
- lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/NavButtonComponent.kt.tt
|
56
58
|
- lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/util/Constants.kt.tt
|
57
59
|
- lib/turbo_native_initializer/templates/android_stack/app/src/main/java/dev/hotwire/turbo/turbonativeproject/util/Extension.kt.tt
|
@@ -73,7 +75,9 @@ files:
|
|
73
75
|
- lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/form_component_submit.xml
|
74
76
|
- lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/fragment_native.xml
|
75
77
|
- lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/fragment_web_home.xml
|
76
|
-
- lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/
|
78
|
+
- lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/menu_component_adapter_row.xml
|
79
|
+
- lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/layout/menu_component_bottom_sheet.xml
|
80
|
+
- lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/menu/web.xml
|
77
81
|
- lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
|
78
82
|
- lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
|
79
83
|
- lib/turbo_native_initializer/templates/android_stack/base/app/src/main/res/mipmap-xhdpi/ic_launcher.png
|
@@ -105,6 +109,8 @@ files:
|
|
105
109
|
- lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/BridgeComponentFactories.kt.tt
|
106
110
|
- lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FlashMessageComponent.kt.tt
|
107
111
|
- lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/FormComponent.kt.tt
|
112
|
+
- lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/MenuComponent.kt.tt
|
113
|
+
- lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/MenuComponentAdapter.kt.tt
|
108
114
|
- lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/strada/NavButtonComponent.kt.tt
|
109
115
|
- lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/util/Constants.kt.tt
|
110
116
|
- lib/turbo_native_initializer/templates/android_tabs/app/src/main/java/dev/hotwire/turbo/turbonativeproject/util/Extension.kt.tt
|
@@ -128,8 +134,10 @@ files:
|
|
128
134
|
- lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/form_component_submit.xml
|
129
135
|
- lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/fragment_native.xml
|
130
136
|
- lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/fragment_web_home.xml
|
131
|
-
- lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/
|
137
|
+
- lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/menu_component_adapter_row.xml
|
138
|
+
- lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/layout/menu_component_bottom_sheet.xml
|
132
139
|
- lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/menu/bottom_navigation_menu.xml
|
140
|
+
- lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/menu/web.xml
|
133
141
|
- lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
|
134
142
|
- lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
|
135
143
|
- lib/turbo_native_initializer/templates/android_tabs/base/app/src/main/res/mipmap-xhdpi/ic_launcher.png
|
@@ -173,6 +181,7 @@ files:
|
|
173
181
|
- lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/BridgeComponent+App.swift
|
174
182
|
- lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/FlashMessageComponent.swift
|
175
183
|
- lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/FormComponent.swift
|
184
|
+
- lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/MenuComponent.swift
|
176
185
|
- lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/Strada/NavButtonComponent.swift
|
177
186
|
- lib/turbo_native_initializer/templates/ios_stack/TurboNativeProject/TurboNativeProject.swift.tt
|
178
187
|
- lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject.xcodeproj/project.pbxproj.tt
|
@@ -200,6 +209,7 @@ files:
|
|
200
209
|
- lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/BridgeComponent+App.swift
|
201
210
|
- lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/FlashMessageComponent.swift
|
202
211
|
- lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/FormComponent.swift
|
212
|
+
- lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/MenuComponent.swift
|
203
213
|
- lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/Strada/NavButtonComponent.swift
|
204
214
|
- lib/turbo_native_initializer/templates/ios_tabs/TurboNativeProject/TurboNativeProject.swift.tt
|
205
215
|
- lib/turbo_native_initializer/version.rb
|
@@ -1,14 +0,0 @@
|
|
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,14 +0,0 @@
|
|
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>
|