diff --git a/demo/src/main/kotlin/dev/hotwire/turbo/demo/base/NavDestination.kt b/demo/src/main/kotlin/dev/hotwire/turbo/demo/base/NavDestination.kt
index 71f26c53..4f8a64ef 100644
--- a/demo/src/main/kotlin/dev/hotwire/turbo/demo/base/NavDestination.kt
+++ b/demo/src/main/kotlin/dev/hotwire/turbo/demo/base/NavDestination.kt
@@ -1,6 +1,7 @@
package dev.hotwire.turbo.demo.base
import android.net.Uri
+import android.view.MenuItem
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
import androidx.browser.customtabs.CustomTabsIntent.SHARE_STATE_ON
@@ -11,10 +12,12 @@ 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
-import dev.hotwire.turbo.nav.TurboNavPresentationContext.*
+import dev.hotwire.turbo.nav.TurboNavPresentationContext.MODAL
interface NavDestination : TurboNavDestination {
+ val menuProgress: MenuItem?
+ get() = toolbarForNavigation()?.menu?.findItem(R.id.menu_progress)
+
override fun shouldNavigateTo(newLocation: String): Boolean {
return when (isNavigable(newLocation)) {
true -> true
diff --git a/demo/src/main/kotlin/dev/hotwire/turbo/demo/features/web/WebBottomSheetFragment.kt b/demo/src/main/kotlin/dev/hotwire/turbo/demo/features/web/WebBottomSheetFragment.kt
index 7008a8b1..0985677c 100644
--- a/demo/src/main/kotlin/dev/hotwire/turbo/demo/features/web/WebBottomSheetFragment.kt
+++ b/demo/src/main/kotlin/dev/hotwire/turbo/demo/features/web/WebBottomSheetFragment.kt
@@ -1,8 +1,28 @@
package dev.hotwire.turbo.demo.features.web
+import android.os.Bundle
+import android.view.View
+import dev.hotwire.turbo.demo.R
import dev.hotwire.turbo.demo.base.NavDestination
import dev.hotwire.turbo.fragments.TurboWebBottomSheetDialogFragment
import dev.hotwire.turbo.nav.TurboNavGraphDestination
@TurboNavGraphDestination(uri = "turbo://fragment/web/modal/sheet")
-class WebBottomSheetFragment : TurboWebBottomSheetDialogFragment(), NavDestination
+class WebBottomSheetFragment : TurboWebBottomSheetDialogFragment(), NavDestination {
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupMenu()
+ }
+
+ override fun onFormSubmissionStarted(location: String) {
+ menuProgress?.isVisible = true
+ }
+
+ override fun onFormSubmissionFinished(location: String) {
+ menuProgress?.isVisible = false
+ }
+
+ private fun setupMenu() {
+ toolbarForNavigation()?.inflateMenu(R.menu.web)
+ }
+}
diff --git a/demo/src/main/kotlin/dev/hotwire/turbo/demo/features/web/WebFragment.kt b/demo/src/main/kotlin/dev/hotwire/turbo/demo/features/web/WebFragment.kt
index 704a4978..1637640d 100644
--- a/demo/src/main/kotlin/dev/hotwire/turbo/demo/features/web/WebFragment.kt
+++ b/demo/src/main/kotlin/dev/hotwire/turbo/demo/features/web/WebFragment.kt
@@ -1,19 +1,38 @@
package dev.hotwire.turbo.demo.features.web
+import android.os.Bundle
+import android.view.View
+import dev.hotwire.turbo.demo.R
import dev.hotwire.turbo.demo.base.NavDestination
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.visit.TurboVisitAction
import dev.hotwire.turbo.visit.TurboVisitAction.REPLACE
import dev.hotwire.turbo.visit.TurboVisitOptions
@TurboNavGraphDestination(uri = "turbo://fragment/web")
open class WebFragment : TurboWebFragment(), NavDestination {
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupMenu()
+ }
+
+ override fun onFormSubmissionStarted(location: String) {
+ menuProgress?.isVisible = true
+ }
+
+ override fun onFormSubmissionFinished(location: String) {
+ menuProgress?.isVisible = false
+ }
+
override fun onVisitErrorReceived(location: String, errorCode: Int) {
when (errorCode) {
401 -> navigate(SIGN_IN_URL, TurboVisitOptions(action = REPLACE))
else -> super.onVisitErrorReceived(location, errorCode)
}
}
+
+ private fun setupMenu() {
+ toolbarForNavigation()?.inflateMenu(R.menu.web)
+ }
}
diff --git a/demo/src/main/res/layout/toolbar_progress.xml b/demo/src/main/res/layout/toolbar_progress.xml
new file mode 100644
index 00000000..7d14dcb3
--- /dev/null
+++ b/demo/src/main/res/layout/toolbar_progress.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/demo/src/main/res/menu/web.xml b/demo/src/main/res/menu/web.xml
new file mode 100644
index 00000000..a96f5cda
--- /dev/null
+++ b/demo/src/main/res/menu/web.xml
@@ -0,0 +1,14 @@
+
+
diff --git a/demo/src/main/res/values/strings.xml b/demo/src/main/res/values/strings.xml
index cd13d4cd..0ddabcc8 100644
--- a/demo/src/main/res/values/strings.xml
+++ b/demo/src/main/res/values/strings.xml
@@ -2,4 +2,5 @@
Turbo Native
Error loading page
The demo server may be starting up. Pull-to-refresh to try again in a minute.
+ Loading…
diff --git a/turbo/src/main/assets/js/turbo_bridge.js b/turbo/src/main/assets/js/turbo_bridge.js
index 1cdd237e..55af01f4 100644
--- a/turbo/src/main/assets/js/turbo_bridge.js
+++ b/turbo/src/main/assets/js/turbo_bridge.js
@@ -144,6 +144,14 @@
})
}
+ formSubmissionStarted(formSubmission) {
+ TurboSession.formSubmissionStarted(formSubmission.location.toString())
+ }
+
+ formSubmissionFinished(formSubmission) {
+ TurboSession.formSubmissionFinished(formSubmission.location.toString())
+ }
+
pageInvalidated() {
TurboSession.pageInvalidated()
}
diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/config/TurboPathConfigurationRepository.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/config/TurboPathConfigurationRepository.kt
index fcb91126..fc8c4b5d 100644
--- a/turbo/src/main/kotlin/dev/hotwire/turbo/config/TurboPathConfigurationRepository.kt
+++ b/turbo/src/main/kotlin/dev/hotwire/turbo/config/TurboPathConfigurationRepository.kt
@@ -6,7 +6,6 @@ import androidx.core.content.edit
import dev.hotwire.turbo.http.TurboHttpClient
import dev.hotwire.turbo.util.dispatcherProvider
import dev.hotwire.turbo.util.toJson
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.Request
import java.io.IOException
diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/delegates/TurboWebFragmentDelegate.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/delegates/TurboWebFragmentDelegate.kt
index 70bf3841..9483558e 100644
--- a/turbo/src/main/kotlin/dev/hotwire/turbo/delegates/TurboWebFragmentDelegate.kt
+++ b/turbo/src/main/kotlin/dev/hotwire/turbo/delegates/TurboWebFragmentDelegate.kt
@@ -231,6 +231,14 @@ internal class TurboWebFragmentDelegate(
return navDestination
}
+ override fun formSubmissionStarted(location: String) {
+ callback.onFormSubmissionStarted(location)
+ }
+
+ override fun formSubmissionFinished(location: String) {
+ callback.onFormSubmissionFinished(location)
+ }
+
// -----------------------------------------------------------------------
// Private
// -----------------------------------------------------------------------
diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/fragments/TurboWebFragmentCallback.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/fragments/TurboWebFragmentCallback.kt
index 0420018c..7ac6d92a 100644
--- a/turbo/src/main/kotlin/dev/hotwire/turbo/fragments/TurboWebFragmentCallback.kt
+++ b/turbo/src/main/kotlin/dev/hotwire/turbo/fragments/TurboWebFragmentCallback.kt
@@ -66,6 +66,16 @@ interface TurboWebFragmentCallback {
*/
fun onVisitErrorReceived(location: String, errorCode: Int) {}
+ /**
+ * Called when a Turbo form submission has started.
+ */
+ fun onFormSubmissionStarted(location: String) {}
+
+ /**
+ * Called when a Turbo form submission has finished.
+ */
+ fun onFormSubmissionFinished(location: String) {}
+
/**
* Called when the Turbo visit resulted in an error, but a cached
* snapshot is being displayed, which may be stale.
diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavRule.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavRule.kt
index 48e07e81..b1953267 100644
--- a/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavRule.kt
+++ b/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavRule.kt
@@ -9,7 +9,6 @@ import dev.hotwire.turbo.config.*
import dev.hotwire.turbo.session.TurboSessionModalResult
import dev.hotwire.turbo.visit.TurboVisitAction
import dev.hotwire.turbo.visit.TurboVisitOptions
-import java.net.URI
@Suppress("MemberVisibilityCanBePrivate")
internal class TurboNavRule(
diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/observers/TurboWindowThemeObserver.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/observers/TurboWindowThemeObserver.kt
index 0ee06abb..999f5e08 100644
--- a/turbo/src/main/kotlin/dev/hotwire/turbo/observers/TurboWindowThemeObserver.kt
+++ b/turbo/src/main/kotlin/dev/hotwire/turbo/observers/TurboWindowThemeObserver.kt
@@ -5,7 +5,6 @@ import android.os.Build
import android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
import android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
import android.view.Window
-import android.view.WindowInsetsController
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
import androidx.annotation.RequiresApi
import androidx.lifecycle.Lifecycle
diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSession.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSession.kt
index df2504af..7707d20c 100644
--- a/turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSession.kt
+++ b/turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSession.kt
@@ -354,6 +354,46 @@ class TurboSession internal constructor(
}
}
+ /**
+ * Called by Turbo bridge when a form submission has started.
+ *
+ * Warning: This method is public so it can be used as a Javascript Interface.
+ * You should never call this directly as it could lead to unintended behavior.
+ *
+ * @param location The location of the form submission.
+ */
+ @JavascriptInterface
+ fun formSubmissionStarted(location: String) {
+ logEvent(
+ "formSubmissionStarted",
+ "location" to location
+ )
+
+ currentVisit?.let {
+ callback { it.formSubmissionStarted(location) }
+ }
+ }
+
+ /**
+ * Called by Turbo bridge when a form submission has finished.
+ *
+ * Warning: This method is public so it can be used as a Javascript Interface.
+ * You should never call this directly as it could lead to unintended behavior.
+ *
+ * @param location The location of the form submission.
+ */
+ @JavascriptInterface
+ fun formSubmissionFinished(location: String) {
+ logEvent(
+ "formSubmissionFinished",
+ "location" to location
+ )
+
+ currentVisit?.let {
+ callback { it.formSubmissionFinished(location) }
+ }
+ }
+
/**
* Called when Turbo bridge detects that the page being visited has been invalidated,
* typically by new resources in the the page HEAD.
diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSessionCallback.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSessionCallback.kt
index b4f40c98..c3940d81 100644
--- a/turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSessionCallback.kt
+++ b/turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSessionCallback.kt
@@ -19,4 +19,6 @@ internal interface TurboSessionCallback {
fun visitLocationStarted(location: String)
fun visitProposedToLocation(location: String, options: TurboVisitOptions)
fun visitNavDestination(): TurboNavDestination
+ fun formSubmissionStarted(location: String)
+ fun formSubmissionFinished(location: String)
}
diff --git a/turbo/src/test/kotlin/dev/hotwire/turbo/session/TurboSessionTest.kt b/turbo/src/test/kotlin/dev/hotwire/turbo/session/TurboSessionTest.kt
index 1620f504..1e756074 100644
--- a/turbo/src/test/kotlin/dev/hotwire/turbo/session/TurboSessionTest.kt
+++ b/turbo/src/test/kotlin/dev/hotwire/turbo/session/TurboSessionTest.kt
@@ -115,6 +115,22 @@ class TurboSessionTest {
assertThat(session.restorationIdentifiers.size()).isEqualTo(1)
}
+ @Test
+ fun visitFormSubmissionStartedFiresCallback() {
+ session.currentVisit = visit
+ session.formSubmissionStarted(visit.location)
+
+ verify(callback).formSubmissionStarted(visit.location)
+ }
+
+ @Test
+ fun visitFormSubmissionFinishedFiresCallback() {
+ session.currentVisit = visit
+ session.formSubmissionFinished(visit.location)
+
+ verify(callback).formSubmissionFinished(visit.location)
+ }
+
@Test
fun pageLoadedSavesRestorationIdentifier() {
val restorationIdentifier = "67890"