-
Notifications
You must be signed in to change notification settings - Fork 12
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
Navigation #239
Merged
mobile-bungalow
merged 32 commits into
liveview-native:main
from
mobile-bungalow:navigation
Nov 26, 2024
Merged
Navigation #239
Changes from 12 commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
81329ff
Cargo.lock
mobile-bungalow 9c5c451
Merge branch 'main' of https://github.com/liveview-native/liveview-na…
mobile-bungalow b564457
Merge branch 'main' of https://github.com/liveview-native/liveview-na…
mobile-bungalow f242756
8 failures, removed pointer chasing
mobile-bungalow cb8fdfc
lints
mobile-bungalow 97b60eb
Merge branch 'main' of https://github.com/liveview-native/liveview-na…
mobile-bungalow ef4094f
7 failing, do not newrender in conversion
mobile-bungalow 8c653ca
clippy
mobile-bungalow cbac04c
Merge branch 'main' of https://github.com/liveview-native/liveview-na…
mobile-bungalow bac2faa
first failing navigation test
mobile-bungalow 320308f
nav files
mobile-bungalow 0be4f69
first test passing
mobile-bungalow 5a95faf
tests passing
mobile-bungalow 0671dfd
remove unnecessary state object
mobile-bungalow 63b53d4
all traversal logic implemented
mobile-bungalow 123caed
tests passing
mobile-bungalow df9df11
more thorough tests
mobile-bungalow 039c008
typo
mobile-bungalow cd8bede
clippy
mobile-bungalow d345c6f
lints
mobile-bungalow 64b9556
clippy passing, api's wrapped
mobile-bungalow 6b71560
start rollback state
mobile-bungalow cedaec0
rollback
mobile-bungalow 6e2f3e0
state rollback works, add the begginning of the actual network code
mobile-bungalow 8bf4464
actually changes page
mobile-bungalow 4260c7c
add session data
mobile-bungalow cb70a14
swift tests
mobile-bungalow 994292c
simple use case kotlin and swift
mobile-bungalow 0349d25
remove generated files
mobile-bungalow 10dc96f
remove short circuiting nav on failure
mobile-bungalow 571b3d2
code review, remove excessive indirection, bubble errors, try lock
mobile-bungalow 991340b
add lock macro
mobile-bungalow File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
mod channel; | ||
mod error; | ||
mod navigation; | ||
mod socket; | ||
|
||
#[cfg(test)] | ||
|
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 |
---|---|---|
@@ -0,0 +1,101 @@ | ||
use reqwest::Url; | ||
|
||
pub type HistoryId = u64; | ||
|
||
#[uniffi::export(callback_interface)] | ||
pub trait NavEventHandler: Send + Sync { | ||
/// This callback instruments events that occur when your user navigates to a | ||
/// new view. You can add serialized metadata to these events as a byte buffer | ||
/// through the [NavOptions] object. | ||
fn handle_event(&self, event: NavEvent) -> HandlerResponse; | ||
} | ||
|
||
#[derive(uniffi::Enum, Clone, Debug, PartialEq, Default)] | ||
pub enum HandlerResponse { | ||
#[default] | ||
Default, | ||
PreventDefault, | ||
} | ||
|
||
#[derive(uniffi::Enum, Clone, Debug, PartialEq)] | ||
pub enum NavEventType { | ||
Push, | ||
Replace, | ||
Reload, | ||
Traverse, | ||
} | ||
|
||
#[derive(uniffi::Record, Clone, Debug, PartialEq)] | ||
pub struct NavHistoryEntry { | ||
/// The target url. | ||
pub url: String, | ||
/// Unique id for this piece of nav entry state. | ||
pub id: HistoryId, | ||
/// state passed in by the user, to be passed in to the navigation event callback. | ||
pub state: Option<Vec<u8>>, | ||
} | ||
|
||
/// An event emitted when the user navigates between views. | ||
#[derive(uniffi::Record, Clone, Debug, PartialEq)] | ||
pub struct NavEvent { | ||
pub event: NavEventType, | ||
pub same_document: bool, | ||
/// The previous location of the page, if there was one | ||
pub from: Option<NavHistoryEntry>, | ||
/// Destination URL | ||
pub to: NavHistoryEntry, | ||
/// Additional user provided metadata handed to the event handler. | ||
pub info: Option<Vec<u8>>, | ||
/// Persistent state kept in history for as long as the even is in the history stack. | ||
pub state: Option<Vec<u8>>, | ||
} | ||
|
||
#[derive(Default, uniffi::Enum)] | ||
pub enum NavAction { | ||
/// Push the navigation event onto the history stack. | ||
#[default] | ||
Push, | ||
/// Replace the current top of the history stack with this navigation event. | ||
Replace, | ||
} | ||
|
||
#[derive(Default, uniffi::Record)] | ||
pub struct NavOptions { | ||
pub action: NavAction, | ||
pub extra_event_info: Option<Vec<u8>>, | ||
pub state: Option<Vec<u8>>, | ||
} | ||
|
||
impl NavEvent { | ||
/// Create a new nav event from the details of a [NavCtx::navigate] event | ||
pub fn new_from_navigate( | ||
new_dest: NavHistoryEntry, | ||
old_dest: Option<NavHistoryEntry>, | ||
opts: NavOptions, | ||
) -> NavEvent { | ||
let event = match opts.action { | ||
NavAction::Push => NavEventType::Push, | ||
NavAction::Replace => NavEventType::Replace, | ||
}; | ||
|
||
let new_url = Url::parse(&new_dest.url).ok(); | ||
let old_url = old_dest | ||
.as_ref() | ||
.and_then(|dest| Url::parse(&dest.url).ok()); | ||
|
||
let same_document = if let (Some(old_url), Some(new_url)) = (old_url, new_url) { | ||
old_url.path() == new_url.path() | ||
} else { | ||
false | ||
}; | ||
|
||
NavEvent { | ||
event, | ||
same_document, | ||
from: old_dest, | ||
to: new_dest, | ||
info: opts.extra_event_info, | ||
state: opts.state, | ||
} | ||
} | ||
} |
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 |
---|---|---|
@@ -0,0 +1,85 @@ | ||
mod ffi; | ||
|
||
use super::socket::LiveSocket; | ||
pub use ffi::*; | ||
use reqwest::Url; | ||
use std::sync::Arc; | ||
|
||
#[derive(Clone)] | ||
struct HandlerInternal(pub Option<Arc<dyn NavEventHandler>>); | ||
|
||
impl std::fmt::Debug for HandlerInternal { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
if self.0.is_some() { | ||
write!(f, "Handler Active")?; | ||
} else { | ||
write!(f, "No Handler Present")?; | ||
}; | ||
Ok(()) | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct NavCtx { | ||
history: Vec<NavHistoryEntry>, | ||
current_id: HistoryId, | ||
navigation_event_handler: HandlerInternal, | ||
current_dest: Option<NavHistoryEntry>, | ||
} | ||
|
||
impl NavHistoryEntry { | ||
pub fn new(url: Url, id: HistoryId, state: Option<Vec<u8>>) -> Self { | ||
Self { | ||
url: url.to_string(), | ||
id, | ||
state, | ||
} | ||
} | ||
} | ||
|
||
impl NavCtx { | ||
pub fn new() -> Self { | ||
Self { | ||
history: vec![], | ||
current_id: 0, | ||
navigation_event_handler: HandlerInternal(None), | ||
current_dest: None, | ||
} | ||
simlay marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/// Navigate to `url` with behavior and metadata specified in `opts`. | ||
pub fn navigate(&mut self, url: Url, opts: NavOptions) { | ||
let next_dest = self.speculative_next_dest(&url, opts.state.clone()); | ||
let event = NavEvent::new_from_navigate(next_dest.clone(), self.current_dest.clone(), opts); | ||
|
||
match self.handle_event(event) { | ||
HandlerResponse::Default => {} | ||
HandlerResponse::PreventDefault => return, | ||
}; | ||
|
||
self.current_id += 1; | ||
self.history.push(next_dest) | ||
} | ||
|
||
pub fn set_event_handler(&mut self, handler: Arc<dyn NavEventHandler>) { | ||
self.navigation_event_handler.0 = Some(handler) | ||
} | ||
|
||
pub fn handle_event(&mut self, event: NavEvent) -> HandlerResponse { | ||
if let Some(handler) = self.navigation_event_handler.0.as_ref() { | ||
handler.handle_event(event) | ||
} else { | ||
HandlerResponse::Default | ||
} | ||
} | ||
|
||
fn speculative_next_dest(&self, url: &Url, state: Option<Vec<u8>>) -> NavHistoryEntry { | ||
NavHistoryEntry { | ||
id: self.current_id + 1, | ||
url: url.to_string(), | ||
state, | ||
} | ||
} | ||
} | ||
|
||
impl LiveSocket {} |
mobile-bungalow marked this conversation as resolved.
Show resolved
Hide resolved
|
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ use std::time::Duration; | |
|
||
use super::*; | ||
mod error; | ||
mod navigation; | ||
mod streaming; | ||
mod upload; | ||
|
||
|
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 |
---|---|---|
@@ -0,0 +1,81 @@ | ||
use std::sync::{Arc, Mutex}; | ||
|
||
use crate::live_socket::navigation::*; | ||
use pretty_assertions::{assert_eq, assert_ne}; | ||
use reqwest::Url; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
// Mock event handler used to validate the internal | ||
// navigation objects state. | ||
pub struct NavigationInspector { | ||
last_event: Mutex<Option<NavEvent>>, | ||
event_ct: Mutex<usize>, | ||
} | ||
|
||
#[derive(Serialize, Deserialize)] | ||
pub struct EventMetadata { | ||
prevent_default: bool, | ||
} | ||
|
||
#[derive(Serialize, Deserialize)] | ||
pub struct HistoryState { | ||
name: String, | ||
} | ||
|
||
impl NavEventHandler for NavigationInspector { | ||
fn handle_event(&self, event: NavEvent) -> HandlerResponse { | ||
*self.last_event.lock().expect("Lock poisoned!") = Some(event); | ||
*self.event_ct.lock().expect("Lock poisoned!") += 1; | ||
HandlerResponse::Default | ||
} | ||
} | ||
|
||
impl NavigationInspector { | ||
pub fn new() -> Self { | ||
Self { | ||
last_event: None.into(), | ||
event_ct: 0.into(), | ||
} | ||
} | ||
|
||
pub fn last_event(&self) -> Option<NavEvent> { | ||
self.last_event.lock().expect("Lock poisoned!").clone() | ||
} | ||
|
||
pub fn event_ct(&self) -> usize { | ||
self.event_ct.lock().expect("Lock poisoned!").clone() | ||
} | ||
} | ||
|
||
#[test] | ||
fn basic_internal_nav() { | ||
let handler = Arc::new(NavigationInspector::new()); | ||
let mut ctx = NavCtx::new(); | ||
ctx.set_event_handler(handler.clone()); | ||
|
||
// sanity check | ||
assert_eq!(handler.event_ct(), 0); | ||
assert!(handler.last_event().is_none()); | ||
|
||
// simple push nav | ||
let url_str = "https://www.website.com/live"; | ||
let url = Url::parse(url_str).expect("URL failed to parse"); | ||
ctx.navigate(url, NavOptions::default()); | ||
|
||
assert_eq!(handler.event_ct(), 1); | ||
assert_eq!( | ||
NavEvent { | ||
info: None, | ||
state: None, | ||
event: NavEventType::Push, | ||
same_document: false, | ||
from: None, | ||
to: NavHistoryEntry { | ||
state: None, | ||
id: 1, | ||
url: url_str.to_string(), | ||
} | ||
}, | ||
handler.last_event().expect("Missing Event") | ||
); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Could this match the name
state
in theNavHistoryEntry
?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.
info
andstate
are two different things, the history entry contains persistent state which will always be included whenever it shows up in a navigation event, or whenNavCtx::current
accquires it.info
is ephemeral and is passed to the event once.I based this off of the web navigation api's naming scheme which is honestly not great.
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.
event_specific_info
might be more descriptive.