Skip to content
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
merged 32 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
81329ff
Cargo.lock
mobile-bungalow Oct 30, 2024
9c5c451
Merge branch 'main' of https://github.com/liveview-native/liveview-na…
mobile-bungalow Nov 6, 2024
b564457
Merge branch 'main' of https://github.com/liveview-native/liveview-na…
mobile-bungalow Nov 14, 2024
f242756
8 failures, removed pointer chasing
mobile-bungalow Nov 16, 2024
cb8fdfc
lints
mobile-bungalow Nov 16, 2024
97b60eb
Merge branch 'main' of https://github.com/liveview-native/liveview-na…
mobile-bungalow Nov 16, 2024
ef4094f
7 failing, do not newrender in conversion
mobile-bungalow Nov 18, 2024
8c653ca
clippy
mobile-bungalow Nov 18, 2024
cbac04c
Merge branch 'main' of https://github.com/liveview-native/liveview-na…
mobile-bungalow Nov 18, 2024
bac2faa
first failing navigation test
mobile-bungalow Nov 18, 2024
320308f
nav files
mobile-bungalow Nov 18, 2024
0be4f69
first test passing
mobile-bungalow Nov 18, 2024
5a95faf
tests passing
mobile-bungalow Nov 19, 2024
0671dfd
remove unnecessary state object
mobile-bungalow Nov 19, 2024
63b53d4
all traversal logic implemented
mobile-bungalow Nov 19, 2024
123caed
tests passing
mobile-bungalow Nov 20, 2024
df9df11
more thorough tests
mobile-bungalow Nov 20, 2024
039c008
typo
mobile-bungalow Nov 20, 2024
cd8bede
clippy
mobile-bungalow Nov 20, 2024
d345c6f
lints
mobile-bungalow Nov 20, 2024
64b9556
clippy passing, api's wrapped
mobile-bungalow Nov 20, 2024
6b71560
start rollback state
mobile-bungalow Nov 21, 2024
cedaec0
rollback
mobile-bungalow Nov 21, 2024
6e2f3e0
state rollback works, add the begginning of the actual network code
mobile-bungalow Nov 21, 2024
8bf4464
actually changes page
mobile-bungalow Nov 22, 2024
4260c7c
add session data
mobile-bungalow Nov 22, 2024
cb70a14
swift tests
mobile-bungalow Nov 22, 2024
994292c
simple use case kotlin and swift
mobile-bungalow Nov 22, 2024
0349d25
remove generated files
mobile-bungalow Nov 22, 2024
10dc96f
remove short circuiting nav on failure
mobile-bungalow Nov 25, 2024
571b3d2
code review, remove excessive indirection, bubble errors, try lock
mobile-bungalow Nov 26, 2024
991340b
add lock macro
mobile-bungalow Nov 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions crates/core/src/live_socket/channel.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
use std::{sync::Arc, time::Duration};

use log::{debug, error};
use phoenix_channels_client::{Channel, Event, Number, Payload, Socket, Topic, JSON};

use super::{LiveSocketError, UploadConfig, UploadError};
use crate::{
diff::fragment::{Root, RootDiff},
Expand All @@ -12,6 +9,8 @@ use crate::{
},
parser::parse,
};
use log::{debug, error};
use phoenix_channels_client::{Channel, Event, Number, Payload, Socket, Topic, JSON};

#[derive(uniffi::Object)]
pub struct LiveChannel {
Expand Down Expand Up @@ -60,6 +59,7 @@ impl LiveFile {

// For non FFI functions
impl LiveChannel {
/// Retrieves the initial document received upon joining the channel.
pub fn join_document(&self) -> Result<Document, LiveSocketError> {
let new_root = match self.join_payload {
Payload::JSONPayload {
Expand Down Expand Up @@ -137,8 +137,8 @@ impl LiveChannel {
Ok(upload_id)
}

// Blocks indefinitely, processing changes to the document using the user provided callback
// In `set_event_handler`
/// Blocks indefinitely, processing changes to the document using the user provided callback
/// In `set_event_handler`
pub async fn merge_diffs(&self) -> Result<(), LiveSocketError> {
// TODO: This should probably take the event closure to send changes back to swift/kotlin
let document = self.document.clone();
Expand Down
3 changes: 2 additions & 1 deletion crates/core/src/live_socket/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
mod channel;
mod error;
mod navigation;
mod socket;

#[cfg(test)]
mod tests;

pub use channel::LiveChannel;
use error::{LiveSocketError, UploadError};
pub use error::{LiveSocketError, UploadError};
pub use socket::LiveSocket;

pub struct UploadConfig {
Expand Down
241 changes: 241 additions & 0 deletions crates/core/src/live_socket/navigation/ffi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
//! # FFI Navigation Types
//!
//! Types and utilities for interacting with the navigation API for the FFI api consumers.
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>>,
Copy link
Collaborator

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 the NavHistoryEntry?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

info and state are two different things, the history entry contains persistent state which will always be included whenever it shows up in a navigation event, or when NavCtx::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.

Copy link
Contributor Author

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.

}

// An action taken with respect to the history stack
// when [NavCtx::navigate] is executed.
#[derive(uniffi::Enum, Default, Clone)]
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,
}

/// Options for calls to [NavCtx::navigate]
#[derive(Default, uniffi::Record)]
pub struct NavOptions {
pub action: NavAction,
pub extra_event_info: Option<Vec<u8>>,
pub state: Option<Vec<u8>>,
}

impl NavEvent {
pub fn new(
event: NavEventType,
to: NavHistoryEntry,
from: Option<NavHistoryEntry>,
info: Option<Vec<u8>>,
) -> Self {
let new_url = Url::parse(&to.url).ok();
let old_url = from.as_ref().and_then(|dest| Url::parse(&dest.url).ok());

let same_document = old_url
.zip(new_url)
.is_some_and(|(old, new)| old.path() == new.path());

NavEvent {
event,
same_document,
from,
to,
info,
}
}

pub fn new_from_reload(dest: NavHistoryEntry, info: Option<Vec<u8>>) -> NavEvent {
NavEvent::new(NavEventType::Reload, dest.clone(), dest.into(), info)
}

/// Create a new nav event from the details of a [NavCtx::traverse_to] event
pub fn new_from_traverse(
new_dest: NavHistoryEntry,
old_dest: Option<NavHistoryEntry>,
info: Option<Vec<u8>>,
) -> NavEvent {
NavEvent::new(NavEventType::Traverse, new_dest, old_dest, info)
}

/// 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,
};

NavEvent::new(event, new_dest, old_dest, opts.extra_event_info)
}

/// Create a new nav event from the details of a [NavCtx::back] event,
/// passing info into the event handler closure.
pub fn new_from_forward(
new_dest: NavHistoryEntry,
old_dest: Option<NavHistoryEntry>,
info: Option<Vec<u8>>,
) -> NavEvent {
NavEvent::new(NavEventType::Push, new_dest, old_dest, info)
}

/// Create a new nav event from the details of a [NavCtx::back] event,
/// passing info into the event handler closure.
pub fn new_from_back(
new_dest: NavHistoryEntry,
old_dest: NavHistoryEntry,
info: Option<Vec<u8>>,
) -> NavEvent {
NavEvent::new(NavEventType::Push, new_dest, Some(old_dest), info)
}
}

use super::{super::error::LiveSocketError, LiveSocket};

#[cfg_attr(not(target_family = "wasm"), uniffi::export(async_runtime = "tokio"))]
impl LiveSocket {
pub async fn navigate(
&self,
url: String,
opts: NavOptions,
) -> Result<Option<HistoryId>, LiveSocketError> {
let url = Url::parse(&url)?;

let mut nav_ctx = self.navigation_ctx.lock().expect("lock poison");
let res = nav_ctx.navigate(url, opts);

if res.is_some() {
//let _ = nav_ctx.current();
//todo!("connect logic")
}

Ok(res)
}

pub async fn reload(
&self,
info: Option<Vec<u8>>,
) -> Result<Option<HistoryId>, LiveSocketError> {
let mut nav_ctx = self.navigation_ctx.lock().expect("lock poison");
let res = nav_ctx.reload(info);
if res.is_some() {
if let Some(_current) = nav_ctx.current() {}
}
Ok(res)
}

pub async fn back(&self, info: Option<Vec<u8>>) -> Result<Option<HistoryId>, LiveSocketError> {
let mut nav_ctx = self.navigation_ctx.lock().expect("lock poison");
let res = nav_ctx.back(info);
if res.is_some() {
if let Some(_current) = nav_ctx.current() {}
}
Ok(res)
}

pub async fn forward(
&self,
info: Option<Vec<u8>>,
) -> Result<Option<HistoryId>, LiveSocketError> {
let mut nav_ctx = self.navigation_ctx.lock().expect("lock poison");
let res = nav_ctx.forward(info);
if res.is_some() {
if let Some(_current) = nav_ctx.current() {}
}
Ok(res)
}

pub async fn traverse_to(
&self,
id: HistoryId,
info: Option<Vec<u8>>,
) -> Result<Option<HistoryId>, LiveSocketError> {
let mut nav_ctx = self.navigation_ctx.lock().expect("lock poison");
let res = nav_ctx.traverse_to(id, info);
if res.is_some() {
if let Some(_current) = nav_ctx.current() {}
}
Ok(res)
}

pub fn can_go_back(&self) -> bool {
let nav_ctx = self.navigation_ctx.lock().expect("lock poison");
nav_ctx.can_go_back()
}

pub fn can_go_forward(&self) -> bool {
let nav_ctx = self.navigation_ctx.lock().expect("lock poison");
nav_ctx.can_go_forward()
}

pub fn can_traverse_to(&self, id: HistoryId) -> bool {
let nav_ctx = self.navigation_ctx.lock().expect("lock poison");
nav_ctx.can_traverse_to(id)
}

pub fn get_entries(&self) -> Vec<NavHistoryEntry> {
let nav_ctx = self.navigation_ctx.lock().expect("lock poison");
nav_ctx.entries()
}

pub fn current(&self) -> Option<NavHistoryEntry> {
let nav_ctx = self.navigation_ctx.lock().expect("lock poison");
nav_ctx.current()
}

pub fn set_event_handler(&self, handler: Box<dyn NavEventHandler>) {
let mut nav_ctx = self.navigation_ctx.lock().expect("lock poison");
nav_ctx.set_event_handler(handler.into())
}
}
Loading