-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Integrate Voyager #87
base: master
Are you sure you want to change the base?
Conversation
# Conflicts: # dependencies.md # pom.xml # version.gradle.kts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Oleg-Melnik 6/14 reviewed so far. Please see my comments. Also, as soon as this change is massive in terms of API changes, please expand the PR description so that "before" and "after" are clearer for readers.
object Voyager { | ||
private const val version = "1.0.0" | ||
private const val group = "cafe.adriel.voyager" | ||
private const val baseName = "voyager" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just name
is enough, given this is a private const.
@@ -98,6 +96,12 @@ public class AppWindow( | |||
*/ | |||
private var bottomDialog by mutableStateOf<Dialog?>(null) | |||
|
|||
/** | |||
* An instance of the screens [Navigator] that will be initialized during |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"screens"? Plural?
Please review.
} | ||
internal fun showScreen( | ||
screen: Screen, | ||
keepCurrentScreenInHistory: Boolean = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keepInHistory
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This parameter is removed at all.
* display is requested while another screen is already displayed. | ||
* @param screen The screen to be shown. | ||
* @param keepCurrentScreenInHistory Specifies whether to save the currently | ||
* visible screen in the history or not. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"or not" is redundant when used in conjunction with whether
. Unless you really-really want to put emphasis on this "or not", which I don't think is the case.
* @throws IllegalStateException | ||
* to indicate the illegal state when another modal screen | ||
* is already displayed. | ||
* @throws IllegalStateException To indicate the illegal state when some |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please explain why this case is an illegal one.
"Cannot display the modal screen when a dialog is displayed." | ||
"Cannot display the screen when a dialog is displayed." | ||
} | ||
checkScreenNavigatorIsInitialized() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's shorten the name of this method.
internal fun closeCurrentScreen() { | ||
checkScreenNavigatorIsInitialized() | ||
check(screenNavigator!!.size > 1) { | ||
"Cannot close the current screen because this is a bottom-most " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If possible, let's add something that would allow the devs to identify the currently displayed screen once this exception is raised.
* | ||
* @param appWindow The main application's window. | ||
*/ | ||
public class Screens |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't this a part of AppWindow
API?
The same question applies to Views
below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Screens
and Views
classes have been removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Oleg-Melnik Please see my thoughts to consider.
* An instance of the screens [Navigator] that will be initialized during | ||
* the rendering of the main window. | ||
*/ | ||
private var screenNavigator: Navigator? = null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd consider just making it a lateinit
non-nullable property because IIUC we expect it to be set upon the very first app window's rendering, and are apparently using it with !!
ever since anyway (accompanied with defensive check...Initialized
calls).
/** | ||
* Displays the given [screen]. | ||
* | ||
* This screen will be rendered using the entire area | ||
* of the application window. No other components | ||
* from other screens will be visible or interactable. | ||
* | ||
* @param screen The screen to be shown. | ||
* @param keepCurrentScreenInHistory Specifies whether to save the currently | ||
* visible screen in the navigation history or not. Default value is `true`. | ||
*/ | ||
public fun show(screen: Screen, keepCurrentScreenInHistory: Boolean = true) { | ||
appWindow.showScreen(screen, keepCurrentScreenInHistory) | ||
} | ||
|
||
/** | ||
* Closes the currently visible screen. | ||
* | ||
* The currently visible screen won't be saved to the navigation history and | ||
* the top-most screen in the history will be displayed. | ||
*/ | ||
public fun closeCurrent() { | ||
appWindow.closeCurrentScreen() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the screens management API in general:
-
Regarding documentation.
As a newcomer, I feel I'm missing information on the model of screen management mechanics in general. I've partially figured it out by checking out the implementation, but here are things where I had and still partially have some doubts, which might IMO be useful to document:- Does the
Application
(or its constituents) track some predefined/custom-supplied list of screens itself, and this is an API for switching between them (similar to how we deal with views)? Or this is just an API that covers the navigation functionality only, while leaving the actual definition of the list of screens, their lifecycle/storage/etc. to be on behalf of the actual application's implementation? - Are screens a one-level notion or they are hierarchical (I know they were one-level and we discussed them to be like this, but I got a little confused by the method pair
show/close
)? This might either be a lack of documentation, or/and the need for correcting an API (I'm metioning it below separately in context of API).- Since we now have both screens and views as part of public API, I believe it would be useful to document both of these notions and their relationship with each other explicitly as well in some general place.
- How exactly does history work in context of having multiple screens and views? IMO it would be useful to describe this in some general form. In particular, I can image having such questions:
- What is the very first screen that is displayed? Can there be no screens displayed at all?
- What if I invoke "show" for the same screen more than once (in a row, or interlaced with showing other screens), without invoking "close" for it? Does it mean pulling that screen from the middle of the deck of screens and putting it on top again? Will the first "show" call for the same screen still remain in history in this case though?
- How does views history relate to screens history (if there's history for views at all)?
- Does the
-
History support in general, the form of API.
Overall, to be honest, my preference regarding the history API would be to refrain from introducing it (at least in a public form) until we have some actual clearly defined needs that we practically have to address. Guessing potential scenarios leads to an opinionated, and possibly excessively complex API that we'll have to deal with, but do we really have a clear picture that we'll need history support in a desktop app?
The thing is, if we don't need history, we could probably keep the API as simple as one property like
currentScreen
!Some potentially possible history-involving scenarios could possibly require alternative forms of APIs, for example, the "keepInHistory" flag to actually be a part of the screen instead, or, possibly, having some app-wide "trackingHistory" flag, which would be turned on/off globally, etc. In other words, we introduce a solution, but what are the problems? And are we sure it's the best/most convenient/adequate form of a solution for those problems?
These are just my thoughts to consider. We can discuss this if needed or you can decide whatever would be appropriate at this point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Screens API will be removed once ImportDomainWizard becomes a dialog.
public fun select(appView: AppView) { | ||
appWindow.selectView(appView) | ||
} | ||
|
||
/** | ||
* Returns the currently selected view. | ||
*/ | ||
public fun current(): AppView = appWindow.currentView() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Depending on anticipated scenarios that we aim to support, I'd also consider a possibility to replace both of these methods with a single mutable property like currentView
. Just a thought to ponder.
@Composable | ||
override fun Content() { | ||
content { | ||
onSuccessAuthentication.invoke() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since onSuccessAuthentication
is not nullable, I'd rather use just onSuccessAuthentication()
for conciseness.
/** | ||
* Represents the Sign-In screen in the application. | ||
*/ | ||
public class SignInScreen( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Do I understand correctly that so far it's only used internally in the library (instantiated to initialize
AppWindow.signInScreen
), and it's not meant to be overridden or used outside of the library? If so, can we make itinternal
? - Actually, if it's really a specifics of implementing
AppWindow
, I'd also consider declaring it inside ofAppWindow.kt
, since having it in a separate file, especially the one in the root package, provokes thoughts on its reusability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Oleg-Melnik see my comments.
My primary concern is explaining to our end-users that difference that we are trying to establish between "views" and "screens". We mix the interfaces and words in our code right now so that it becomes difficult to comprehend.
@@ -56,6 +57,10 @@ import io.spine.chords.core.Component | |||
* It is equally possible to declare [AppView] subclasses as regular classes | |||
* if needed. | |||
* | |||
* The [AppView] implements the [Screen] interface, enabling the use of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, it's the same question regarding exposing Voyager API. If AppView
is public
, and it acts as a Voyager's Screen
, then end-users will need Voyager in their classpath. Hence, it must be api
.
Please correct me if I am wrong.
check(currentScreen == mainScreen) { | ||
"Another modal screen is visible already." | ||
} | ||
internal fun showScreen(screen: Screen) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we shorten it to show(screen: Screen)
?
@@ -196,3 +221,18 @@ public class AppWindow( | |||
} | |||
} | |||
} | |||
|
|||
/** | |||
* Represents the Sign-In screen in the application. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's simplify without this pretentious "Represents":
A sign-in screen of the application.
} | ||
|
||
/** | ||
* Returns the currently selected view. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be a place somewhere in the code docs where we explain the difference between a view and a screen. Or telling that this is the same thing (which it is not, as far as we discussed).
Current API of AppView
and the API of this type are confusing: they have both "show..." methods working with Screen
s and currentView
returning AppView
(which is ALSO as Screen
).
pom.xml
Outdated
<dependency> | ||
<groupId>cafe.adriel.voyager</groupId> | ||
<artifactId>voyager-screenmodel</artifactId> | ||
<version>1.0.0</version> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They have 1.0.1
, which I believe is a bugfix release.
# Conflicts: # dependencies.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Oleg-Melnik LGTM in general. However, please see my comments.
core/build.gradle.kts
Outdated
implementation(compose.desktop.currentOs) | ||
implementation(Material3.Desktop.lib) | ||
api(Voyager.navigator) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this to be the first item. It's more logical, because api
-level dependencies are transitive.
@@ -56,6 +57,10 @@ import io.spine.chords.core.Component | |||
* It is equally possible to declare [AppView] subclasses as regular classes | |||
* if needed. | |||
* | |||
* Implements the [Screen] interface, enabling the use of navigation | |||
* functionality provided by the Voyager multiplatform navigation library. | |||
* See [AppWindow] on how to select some [AppView] to be displayed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to provide the link to some particular method, not just to a type.
* | ||
* @param signInScreenContent A content for the sign-in screen. | ||
* @param views The list of application's views. | ||
* Provides two levels of navigation enabled by the Voyager multiplatform |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's simplify:
* Powered by Voyager, provides two levels of navigation:
*
* 1.The screen-level navigation. Allows selecting a screen
* to fill the entire main window.
*
* Currently, there is no public API available to display a custom screen.
* Instead, two screens are predefined: [SignInScreen] and [MainScreen].
* Please refer to their description for more detail.
* In the future, more screen implementations may be created.
*
* 2. The view-level navigation. Allows selecting an [AppView] to be displayed
* on the `MainScreen`.
* Please refer to [ApplicationUI] <TODO: can we point to the method here?>
* for details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Oleg-Melnik LGTM with just a small idea to consider.
public fun select(appView: AppView) { | ||
appWindow.select(appView) | ||
} | ||
|
||
/** | ||
* Closes the currently visible modal screen. | ||
* | ||
* @throws IllegalStateException | ||
* to indicate the illegal state when no modal screen to close. | ||
* Returns the currently selected [AppView] on the [MainScreen]. | ||
*/ | ||
public fun closeCurrentModalScreen() { | ||
appWindow.closeCurrentModalScreen() | ||
} | ||
public val currentView: AppView get() = appWindow.currentView |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Oleg-Melnik I think you've considered this, and there probably were some arguments for having the select()
method separately, but, just in case, I'd like to pinpoint an alternative that might seem more natural if no changes to select
are planned in the future:
we could actually replace select
with turning currentView
into a mutable property and having the respective functionality in its setter.
This PR integrates the Voyager into the application.
From now on, Chords provides two levels of navigation enabled by the Voyager multiplatform navigation library. The first level is screen-level navigation, which allows selecting a screen to be displayed across the entire main window. Currently, two screens can be displayed:
SignInScreen
for user authentication and theMainScreen
, which is shown when a user is logged into the system. There is no public API available to display a custom screen at the moment. This may change in the future as relevant use-cases appear.The second level — for selecting an
AppView
to be displayed on theMainScreen
.The
ApplicationUI
, which is a public API for end user, has been updated with the following changes:select(View)
andcurrentView
methods to manage application views.show(Screen)
andcloseCurrentScreen
have been removed sinceDomainImportWizard
has become a dialog and there are no more use-cases to support Screen API.Currently the
ApplicationUI
looks like the following: