Skip to content

Commit

Permalink
Merge branch 'main' into anim_replace_root
Browse files Browse the repository at this point in the history
* main:
  Fix test
  Log whether a visit that has started is a page refresh
  Rename method for consistency with scrolling to anchor proposals
  Use nullable location reference
  Check for window.Turbo and add clarifying comment
  Fix the implementation so it properly works for Turbo 8 refreshes without any flickering. Use Turbo.navigator.location to determine the current location, so it works immediately cold boots and Turbo visits
  Bump minSdkVersion to 26 in the docs
  REPLACE_ROOT presentation - popUpTo to controller.graph.id (inclusive)
  Bump minSdkVersion to 26
  Upgrade to SDK 34 and bump dependencies to latest versions
  Tweak the comment, since not all visit proposals will make it to the app
  After a form redirect to the same page location, don't replace the existing fragment with a new one for a visit
  Use the public strada dependency
  Add the overflow-menu component implementation
  Add the menu component implementation
  Configure the strada form path as a modal screen
  Add the form component implementation
  Intitial work to tie in Strada support with component examples
  Improve how debug logging is enabled and how exceptions are logged

# Conflicts:
#	turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavRule.kt
  • Loading branch information
jayohms committed Feb 23, 2024
2 parents a83575d + 8556f9d commit 1732239
Show file tree
Hide file tree
Showing 36 changed files with 629 additions and 88 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

## Requirements

1. Android SDK 24+ is required as the `minSdkVersion` in your build.gradle.
1. Android SDK 26+ is required as the `minSdkVersion` in your build.gradle.
1. This library is written entirely in [Kotlin](https://kotlinlang.org/), and your app should use Kotlin as well. Compatibility with Java is not provided or supported.
1. This library supports web apps using either Turbo 7 or Turbolinks 5.
1. `Turbo` (or `Turbolinks`) is exposed on the `window` object on the WebView page being loaded.
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:8.1.0'
classpath 'com.android.tools.build:gradle:8.1.4'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0'
}
}
Expand Down
30 changes: 24 additions & 6 deletions demo/build.gradle
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
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
compileSdk 34

defaultConfig {
applicationId "dev.hotwire.turbo.demo"
minSdkVersion 24
targetSdkVersion 33
minSdkVersion 26
targetSdkVersion 34
versionCode 1
versionName "1.0"
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.10.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.browser:browser:1.5.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.browser:browser:1.7.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
16 changes: 16 additions & 0 deletions demo/src/main/kotlin/dev/hotwire/turbo/demo/main/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
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
import dev.hotwire.turbo.delegates.TurboActivityDelegate
import dev.hotwire.turbo.demo.R

Expand All @@ -14,5 +19,16 @@ class MainActivity : AppCompatActivity(), TurboActivity {
setContentView(R.layout.activity_main)

delegate = TurboActivityDelegate(this, R.id.main_nav_host)
configApp()
}

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

if (BuildConfig.DEBUG) {
Turbo.config.debugLoggingEnabled = true
Strada.config.debugLoggingEnabled = true
WebView.setWebContentsDebuggingEnabled(true)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package dev.hotwire.turbo.demo.main

import android.webkit.WebView
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import dev.hotwire.turbo.BuildConfig
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 All @@ -13,6 +12,7 @@ import dev.hotwire.turbo.demo.features.web.WebFragment
import dev.hotwire.turbo.demo.features.web.WebHomeFragment
import dev.hotwire.turbo.demo.features.web.WebModalFragment
import dev.hotwire.turbo.demo.util.HOME_URL
import dev.hotwire.turbo.demo.util.customUserAgent
import dev.hotwire.turbo.demo.util.initDayNightTheme
import dev.hotwire.turbo.session.TurboSessionNavHostFragment
import kotlin.reflect.KClass
Expand Down Expand Up @@ -44,16 +44,12 @@ class MainSessionNavHostFragment : TurboSessionNavHostFragment() {

override fun onSessionCreated() {
super.onSessionCreated()
session.webView.settings.userAgentString = customUserAgent(session.webView)
session.webView.initDayNightTheme()

if (BuildConfig.DEBUG) {
session.setDebugLoggingEnabled(true)
WebView.setWebContentsDebuggingEnabled(true)
}
}
// Configure WebView
session.webView.settings.userAgentString = session.webView.customUserAgent
session.webView.initDayNightTheme()

private fun customUserAgent(webView: WebView): String {
return "Turbo Native Android ${webView.settings.userAgentString}"
// 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
)
}
Loading

0 comments on commit 1732239

Please sign in to comment.