-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #48 from hotwired/consolidate-fragments
Consolidate old Turbo* fragments into new *Hotwire fragments
- Loading branch information
Showing
14 changed files
with
523 additions
and
564 deletions.
There are no files selected for viewing
123 changes: 121 additions & 2 deletions
123
core/src/main/kotlin/dev/hotwire/core/navigation/fragments/HotwireBottomSheetFragment.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,124 @@ | ||
package dev.hotwire.core.navigation.fragments | ||
|
||
import dev.hotwire.core.turbo.fragments.TurboBottomSheetDialogFragment | ||
import android.content.DialogInterface | ||
import android.content.Intent | ||
import android.os.Bundle | ||
import android.view.View | ||
import androidx.appcompat.widget.Toolbar | ||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment | ||
import dev.hotwire.core.R | ||
import dev.hotwire.core.navigation.navigator.Navigator | ||
import dev.hotwire.core.navigation.navigator.NavigatorHost | ||
import dev.hotwire.core.turbo.config.title | ||
import dev.hotwire.core.turbo.nav.HotwireNavDestination | ||
import dev.hotwire.core.turbo.nav.HotwireNavDialogDestination | ||
|
||
abstract class HotwireBottomSheetFragment : TurboBottomSheetDialogFragment() | ||
/** | ||
* The base class from which all bottom sheet native fragments in a | ||
* Hotwire app should extend from. | ||
* | ||
* For web bottom sheet fragments, refer to [HotwireWebBottomSheetFragment]. | ||
*/ | ||
abstract class HotwireBottomSheetFragment : BottomSheetDialogFragment(), | ||
HotwireNavDestination, HotwireNavDialogDestination { | ||
override lateinit var navigator: Navigator | ||
internal lateinit var delegate: HotwireFragmentDelegate | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
navigator = (parentFragment as NavigatorHost).navigator | ||
delegate = HotwireFragmentDelegate(this) | ||
} | ||
|
||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
super.onViewCreated(view, savedInstanceState) | ||
delegate.onViewCreated() | ||
|
||
if (shouldObserveTitleChanges()) { | ||
observeTitleChanges() | ||
pathProperties.title?.let { | ||
fragmentViewModel.setTitle(it) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* This is marked `final` to prevent further use, as it's now deprecated in | ||
* AndroidX's Fragment implementation. | ||
* | ||
* Use [onViewCreated] for code touching | ||
* the Fragment's view and [onCreate] for other initialization. | ||
*/ | ||
@Suppress("DEPRECATION") | ||
final override fun onActivityCreated(savedInstanceState: Bundle?) { | ||
super.onActivityCreated(savedInstanceState) | ||
} | ||
|
||
/** | ||
* This is marked `final` to prevent further use, as it's now deprecated in | ||
* AndroidX's Fragment implementation. | ||
* | ||
* Use [registerForActivityResult] with the appropriate | ||
* [androidx.activity.result.contract.ActivityResultContract] and its callback. | ||
* | ||
* Turbo provides the [HotwireNavDestination.activityResultLauncher] interface | ||
* to obtain registered result launchers from any destination. | ||
*/ | ||
@Suppress("DEPRECATION") | ||
final override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { | ||
super.onActivityResult(requestCode, resultCode, intent) | ||
} | ||
|
||
override fun onStart() { | ||
super.onStart() | ||
delegate.onStart() | ||
} | ||
|
||
override fun onStop() { | ||
super.onStop() | ||
delegate.onStop() | ||
} | ||
|
||
override fun onCancel(dialog: DialogInterface) { | ||
delegate.onDialogCancel() | ||
super.onCancel(dialog) | ||
} | ||
|
||
override fun onDismiss(dialog: DialogInterface) { | ||
delegate.onDialogDismiss() | ||
super.onDismiss(dialog) | ||
} | ||
|
||
override fun closeDialog() { | ||
requireDialog().cancel() | ||
} | ||
|
||
override fun onBeforeNavigation() {} | ||
|
||
override fun refresh(displayProgress: Boolean) {} | ||
|
||
override fun prepareNavigation(onReady: () -> Unit) { | ||
delegate.prepareNavigation(onReady) | ||
} | ||
|
||
/** | ||
* Gets the Toolbar instance in your Fragment's view for use with | ||
* navigation. The title in the Toolbar will automatically be | ||
* updated if a title is available. By default, Turbo will look | ||
* for a Toolbar with resource ID `R.id.toolbar`. Override to | ||
* provide a Toolbar instance with a different ID. | ||
*/ | ||
override fun toolbarForNavigation(): Toolbar? { | ||
return view?.findViewById(R.id.toolbar) | ||
} | ||
|
||
final override fun delegate(): HotwireFragmentDelegate { | ||
return delegate | ||
} | ||
|
||
private fun observeTitleChanges() { | ||
fragmentViewModel.title.observe(viewLifecycleOwner) { | ||
toolbarForNavigation()?.title = it | ||
} | ||
} | ||
} |
171 changes: 169 additions & 2 deletions
171
core/src/main/kotlin/dev/hotwire/core/navigation/fragments/HotwireFragment.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,172 @@ | ||
package dev.hotwire.core.navigation.fragments | ||
|
||
import dev.hotwire.core.turbo.fragments.TurboFragment | ||
import android.content.Intent | ||
import android.os.Bundle | ||
import android.view.View | ||
import androidx.appcompat.widget.Toolbar | ||
import androidx.fragment.app.Fragment | ||
import dev.hotwire.core.R | ||
import dev.hotwire.core.navigation.navigator.Navigator | ||
import dev.hotwire.core.navigation.navigator.NavigatorHost | ||
import dev.hotwire.core.turbo.config.context | ||
import dev.hotwire.core.turbo.config.title | ||
import dev.hotwire.core.turbo.nav.HotwireNavDestination | ||
import dev.hotwire.core.turbo.nav.TurboNavPresentationContext | ||
import dev.hotwire.core.turbo.observers.HotwireWindowThemeObserver | ||
import dev.hotwire.core.turbo.session.SessionModalResult | ||
|
||
abstract class HotwireFragment : TurboFragment() | ||
/** | ||
* The base class from which all "standard" native Fragments (non-dialogs) in a | ||
* Turbo-driven app should extend from. | ||
* | ||
* For web fragments, refer to [HotwireWebFragment]. | ||
*/ | ||
abstract class HotwireFragment : Fragment(), HotwireNavDestination { | ||
override lateinit var navigator: Navigator | ||
internal lateinit var delegate: HotwireFragmentDelegate | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
navigator = (parentFragment as NavigatorHost).navigator | ||
delegate = HotwireFragmentDelegate(this) | ||
} | ||
|
||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
super.onViewCreated(view, savedInstanceState) | ||
delegate.onViewCreated() | ||
|
||
observeModalResult() | ||
observeDialogResult() | ||
observeTheme() | ||
|
||
if (shouldObserveTitleChanges()) { | ||
observeTitleChanges() | ||
pathProperties.title?.let { | ||
fragmentViewModel.setTitle(it) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* This is marked `final` to prevent further use, as it's now deprecated in | ||
* AndroidX's Fragment implementation. | ||
* | ||
* Use [onViewCreated] for code touching | ||
* the Fragment's view and [onCreate] for other initialization. | ||
*/ | ||
@Suppress("DEPRECATION") | ||
final override fun onActivityCreated(savedInstanceState: Bundle?) { | ||
super.onActivityCreated(savedInstanceState) | ||
} | ||
|
||
/** | ||
* This is marked `final` to prevent further use, as it's now deprecated in | ||
* AndroidX's Fragment implementation. | ||
* | ||
* Use [registerForActivityResult] with the appropriate | ||
* [androidx.activity.result.contract.ActivityResultContract] and its callback. | ||
* | ||
* Turbo provides the [HotwireNavDestination.activityResultLauncher] interface | ||
* to obtain registered result launchers from any destination. | ||
*/ | ||
@Suppress("DEPRECATION") | ||
final override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { | ||
super.onActivityResult(requestCode, resultCode, intent) | ||
} | ||
|
||
override fun onStart() { | ||
super.onStart() | ||
|
||
if (!delegate.sessionViewModel.modalResultExists) { | ||
delegate.onStart() | ||
} | ||
} | ||
|
||
override fun onStop() { | ||
super.onStop() | ||
delegate.onStop() | ||
} | ||
|
||
override fun prepareNavigation(onReady: () -> Unit) { | ||
delegate.prepareNavigation(onReady) | ||
} | ||
|
||
/** | ||
* Called when the Fragment has been started again after receiving a | ||
* modal result. Will navigate if the result indicates it should. | ||
*/ | ||
open fun onStartAfterModalResult(result: SessionModalResult) { | ||
delegate.onStartAfterModalResult(result) | ||
} | ||
|
||
/** | ||
* Called when the Fragment has been started again after a dialog has | ||
* been dismissed/canceled and no result is passed back. | ||
*/ | ||
open fun onStartAfterDialogCancel() { | ||
if (!delegate.sessionViewModel.modalResultExists) { | ||
delegate.onStartAfterDialogCancel() | ||
} | ||
} | ||
|
||
override fun onBeforeNavigation() {} | ||
|
||
override fun refresh(displayProgress: Boolean) {} | ||
|
||
/** | ||
* Gets the Toolbar instance in your Fragment's view for use with | ||
* navigation. The title in the Toolbar will automatically be | ||
* updated if a title is available. By default, Turbo will look | ||
* for a Toolbar with resource ID `R.id.toolbar`. Override to | ||
* provide a Toolbar instance with a different ID. | ||
*/ | ||
override fun toolbarForNavigation(): Toolbar? { | ||
return view?.findViewById(R.id.toolbar) | ||
} | ||
|
||
final override fun delegate(): HotwireFragmentDelegate { | ||
return delegate | ||
} | ||
|
||
private fun observeModalResult() { | ||
if (shouldHandleModalResults()) { | ||
delegate.sessionViewModel.modalResult.observe(viewLifecycleOwner) { event -> | ||
event.getContentIfNotHandled()?.let { | ||
onStartAfterModalResult(it) | ||
} | ||
} | ||
} | ||
} | ||
|
||
private fun observeDialogResult() { | ||
delegate.sessionViewModel.dialogResult.observe(viewLifecycleOwner) { event -> | ||
event.getContentIfNotHandled()?.let { | ||
onStartAfterDialogCancel() | ||
} | ||
} | ||
} | ||
|
||
private fun observeTitleChanges() { | ||
fragmentViewModel.title.observe(viewLifecycleOwner) { | ||
toolbarForNavigation()?.title = it | ||
} | ||
} | ||
|
||
/* | ||
* If a theme is applied directly on the root view, allow the | ||
* system status and navigation bars to inherit the view's theme | ||
* and override the Activity's theme window attributes. | ||
*/ | ||
private fun observeTheme() { | ||
val view = view ?: return | ||
|
||
if (requireActivity().theme != view.context.theme) { | ||
viewLifecycleOwner.lifecycle.addObserver(HotwireWindowThemeObserver(this)) | ||
} | ||
} | ||
|
||
private fun shouldHandleModalResults(): Boolean { | ||
// Only handle modal results in non-modal contexts | ||
return pathProperties.context != TurboNavPresentationContext.MODAL | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.