Skip to content

Commit

Permalink
Merge pull request #288 from hotwired/strada-sample
Browse files Browse the repository at this point in the history
Strada sample components in the Demo app
  • Loading branch information
jayohms authored Sep 20, 2023
2 parents f40d830 + 6f4bd8c commit bf945e1
Show file tree
Hide file tree
Showing 18 changed files with 472 additions and 5 deletions.
22 changes: 20 additions & 2 deletions demo/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'

buildscript {
repositories {
google()
mavenCentral()
}

dependencies {
classpath "org.jetbrains.kotlin:kotlin-serialization:1.8.0"
}
}

android {
compileSdkVersion 33
Expand All @@ -13,6 +25,10 @@ android {
vectorDrawables.useSupportLibrary = true
}

buildFeatures {
viewBinding = true
}

buildTypes {
release {
minifyEnabled false
Expand Down Expand Up @@ -49,11 +65,13 @@ android {

dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.google.android.material:material:1.8.0'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.recyclerview:recyclerview:1.3.1'
implementation 'androidx.browser:browser:1.5.0'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
implementation 'com.github.bumptech.glide:glide:4.15.1'
implementation 'dev.hotwire:strada:1.0.0-beta2'

implementation project(':turbo')
}
Expand Down
3 changes: 2 additions & 1 deletion demo/src/main/assets/json/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
},
{
"patterns": [
"/signin$"
"/signin$",
"/strada-form$"
],
"properties": {
"context": "modal",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import androidx.browser.customtabs.CustomTabsIntent
import androidx.browser.customtabs.CustomTabsIntent.SHARE_STATE_ON
import androidx.navigation.NavOptions
import androidx.navigation.navOptions
import dev.hotwire.strada.BridgeDestination
import dev.hotwire.turbo.config.TurboPathConfigurationProperties
import dev.hotwire.turbo.config.context
import dev.hotwire.turbo.demo.R
import dev.hotwire.turbo.demo.util.BASE_URL
import dev.hotwire.turbo.nav.TurboNavDestination
import dev.hotwire.turbo.nav.TurboNavPresentationContext.MODAL

interface NavDestination : TurboNavDestination {
interface NavDestination : TurboNavDestination, BridgeDestination {
val menuProgress: MenuItem?
get() = toolbarForNavigation()?.menu?.findItem(R.id.menu_progress)

Expand All @@ -38,6 +39,10 @@ interface NavDestination : TurboNavDestination {
}
}

override fun bridgeWebViewIsReady(): Boolean {
return session.isReady
}

private fun isNavigable(location: String): Boolean {
return location.startsWith(BASE_URL)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.hotwire.turbo.demo.features.numbers

import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
Expand All @@ -11,6 +12,7 @@ class NumbersAdapter(val callback: NumbersFragmentCallback) : RecyclerView.Adapt
private val type = R.layout.adapter_numbers_row

private var items = emptyList<Int>()
@SuppressLint("NotifyDataSetChanged")
set(value) {
field = value
notifyDataSetChanged()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,52 @@ package dev.hotwire.turbo.demo.features.web

import android.os.Bundle
import android.view.View
import dev.hotwire.strada.BridgeDelegate
import dev.hotwire.turbo.demo.R
import dev.hotwire.turbo.demo.base.NavDestination
import dev.hotwire.turbo.demo.strada.bridgeComponentFactories
import dev.hotwire.turbo.demo.util.SIGN_IN_URL
import dev.hotwire.turbo.fragments.TurboWebFragment
import dev.hotwire.turbo.nav.TurboNavGraphDestination
import dev.hotwire.turbo.views.TurboWebView
import dev.hotwire.turbo.visit.TurboVisitAction.REPLACE
import dev.hotwire.turbo.visit.TurboVisitOptions

@TurboNavGraphDestination(uri = "turbo://fragment/web")
open class WebFragment : TurboWebFragment(), NavDestination {
private val bridgeDelegate by lazy {
BridgeDelegate(
location = location,
destination = this,
componentFactories = bridgeComponentFactories
)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupMenu()
viewLifecycleOwner.lifecycle.addObserver(bridgeDelegate)
}

override fun onDestroyView() {
super.onDestroyView()
viewLifecycleOwner.lifecycle.removeObserver(bridgeDelegate)
}

override fun onColdBootPageStarted(location: String) {
bridgeDelegate.onColdBootPageStarted()
}

override fun onColdBootPageCompleted(location: String) {
bridgeDelegate.onColdBootPageCompleted()
}

override fun onWebViewAttached(webView: TurboWebView) {
bridgeDelegate.onWebViewAttached(webView)
}

override fun onWebViewDetached(webView: TurboWebView) {
bridgeDelegate.onWebViewDetached()
}

override fun onFormSubmissionStarted(location: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package dev.hotwire.turbo.demo.main
import android.os.Bundle
import android.webkit.WebView
import androidx.appcompat.app.AppCompatActivity
import dev.hotwire.strada.KotlinXJsonConverter
import dev.hotwire.strada.Strada
import dev.hotwire.turbo.BuildConfig
import dev.hotwire.turbo.activities.TurboActivity
import dev.hotwire.turbo.config.Turbo
Expand All @@ -21,8 +23,11 @@ class MainActivity : AppCompatActivity(), TurboActivity {
}

private fun configApp() {
Strada.config.jsonConverter = KotlinXJsonConverter()

if (BuildConfig.DEBUG) {
Turbo.config.debugLoggingEnabled = true
Strada.config.debugLoggingEnabled = true
WebView.setWebContentsDebuggingEnabled(true)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dev.hotwire.turbo.demo.main

import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import dev.hotwire.strada.Bridge
import dev.hotwire.turbo.config.TurboPathConfiguration
import dev.hotwire.turbo.demo.features.imageviewer.ImageViewerFragment
import dev.hotwire.turbo.demo.features.numbers.NumberBottomSheetFragment
Expand Down Expand Up @@ -43,7 +44,12 @@ class MainSessionNavHostFragment : TurboSessionNavHostFragment() {

override fun onSessionCreated() {
super.onSessionCreated()

// Configure WebView
session.webView.settings.userAgentString = session.webView.customUserAgent
session.webView.initDayNightTheme()

// Initialize Strada bridge with new WebView instance
Bridge.initialize(session.webView)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.hotwire.turbo.demo.strada

import dev.hotwire.strada.BridgeComponentFactory

val bridgeComponentFactories = listOf(
BridgeComponentFactory("form", ::FormComponent),
BridgeComponentFactory("menu", ::MenuComponent),
BridgeComponentFactory("overflow-menu", ::OverflowMenuComponent)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package dev.hotwire.turbo.demo.strada

import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import dev.hotwire.strada.BridgeComponent
import dev.hotwire.strada.BridgeDelegate
import dev.hotwire.strada.Message
import dev.hotwire.turbo.demo.R
import dev.hotwire.turbo.demo.base.NavDestination
import dev.hotwire.turbo.demo.databinding.FormComponentSubmitBinding
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/**
* Bridge component to display a submit button in the native toolbar,
* which will submit the form on the page when tapped.
*/
class FormComponent(
name: String,
private val delegate: BridgeDelegate<NavDestination>
) : BridgeComponent<NavDestination>(name, delegate) {

private val submitButtonItemId = 37
private var submitMenuItem: MenuItem? = null
private val fragment: Fragment
get() = delegate.destination.fragment
private val toolbar: Toolbar?
get() = fragment.view?.findViewById(R.id.toolbar)

override fun onReceive(message: Message) {
when (message.event) {
"connect" -> handleConnectEvent(message)
"submitEnabled" -> handleSubmitEnabled()
"submitDisabled" -> handleSubmitDisabled()
else -> Log.w("TurboDemo", "Unknown event for message: $message")
}
}

private fun handleConnectEvent(message: Message) {
val data = message.data<MessageData>() ?: return
showToolbarButton(data)
}

private fun handleSubmitEnabled() {
toggleSubmitButton(true)
}

private fun handleSubmitDisabled() {
toggleSubmitButton(false)
}

private fun showToolbarButton(data: MessageData) {
val menu = toolbar?.menu ?: return
val inflater = LayoutInflater.from(fragment.requireContext())
val binding = FormComponentSubmitBinding.inflate(inflater)
val order = 999 // Show as the right-most button

binding.formSubmit.apply {
text = data.title
setOnClickListener {
performSubmit()
}
}

menu.removeItem(submitButtonItemId)
submitMenuItem = menu.add(Menu.NONE, submitButtonItemId, order, data.title).apply {
actionView = binding.root
setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
}
}

private fun toggleSubmitButton(enable: Boolean) {
val layout = submitMenuItem?.actionView ?: return

FormComponentSubmitBinding.bind(layout).apply {
formSubmit.isEnabled = enable
}
}

private fun performSubmit(): Boolean {
return replyTo("connect")
}

@Serializable
data class MessageData(
@SerialName("submitTitle") val title: String
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package dev.hotwire.turbo.demo.strada

import android.util.Log
import android.view.LayoutInflater
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetDialog
import dev.hotwire.strada.BridgeComponent
import dev.hotwire.strada.BridgeDelegate
import dev.hotwire.strada.Message
import dev.hotwire.turbo.demo.base.NavDestination
import dev.hotwire.turbo.demo.databinding.MenuComponentBottomSheetBinding
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/**
* Bridge component to display a native bottom sheet menu, which will
* send the selected index of the tapped menu item back to the web.
*/
class MenuComponent(
name: String,
private val delegate: BridgeDelegate<NavDestination>
) : BridgeComponent<NavDestination>(name, delegate) {

private val fragment: Fragment
get() = delegate.destination.fragment

override fun onReceive(message: Message) {
when (message.event) {
"display" -> handleDisplayEvent(message)
else -> Log.w("TurboDemo", "Unknown event for message: $message")
}
}

private fun handleDisplayEvent(message: Message) {
val data = message.data<MessageData>() ?: return
showBottomSheet(data.title, data.items)
}

private fun showBottomSheet(title: String, items: List<Item>) {
val view = fragment.view?.rootView ?: return
val inflater = LayoutInflater.from(view.context)
val bottomSheet = BottomSheetDialog(view.context)
val binding = MenuComponentBottomSheetBinding.inflate(inflater)

binding.toolbar.title = title
binding.recyclerView.layoutManager = LinearLayoutManager(view.context)
binding.recyclerView.adapter = MenuComponentAdapter().apply {
setData(items)
setListener {
bottomSheet.dismiss()
onItemSelected(it)
}
}

bottomSheet.apply {
setContentView(binding.root)
show()
}
}

private fun onItemSelected(item: Item) {
replyTo("display", SelectionMessageData(item.index))
}

@Serializable
data class MessageData(
@SerialName("title") val title: String,
@SerialName("items") val items: List<Item>
)

@Serializable
data class Item(
@SerialName("title") val title: String,
@SerialName("index") val index: Int
)

@Serializable
data class SelectionMessageData(
@SerialName("selectedIndex") val selectedIndex: Int
)
}
Loading

0 comments on commit bf945e1

Please sign in to comment.