use std::fmt::Debug;
pub trait Route: Clone + Debug + PartialEq {
fn launch_mode(&self) -> LaunchMode;
fn is_translucent(&self) -> bool;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LaunchMode {
Standard,
SingleTop,
SingleTask,
SingleInstance,
}
pub trait ActivityHost: Sized + 'static {
type Message: Send + Debug;
type Context;
type Route: Route;
type View<'a>
where
Self: 'a;
type Task;
fn empty_task() -> Self::Task;
fn batch_tasks(tasks: Vec<Self::Task>) -> Self::Task;
}
pub trait Activity<H: ActivityHost>: Send {
fn view<'a>(&'a self, context: &'a H::Context) -> H::View<'a>;
fn update(&mut self, _context: &mut H::Context, _message: H::Message) -> H::Task {
H::empty_task()
}
fn on_create(&mut self, _context: &mut H::Context) -> H::Task {
H::empty_task()
}
fn on_resume(&mut self, _context: &mut H::Context) -> H::Task {
H::empty_task()
}
fn on_pause(&mut self, _context: &mut H::Context) -> H::Task {
H::empty_task()
}
fn on_destroy(&mut self, _context: &mut H::Context) -> H::Task {
H::empty_task()
}
fn on_new_intent(&mut self, _context: &mut H::Context, _route: H::Route) -> H::Task {
H::empty_task()
}
fn route(&self) -> H::Route;
}
pub struct ActivityManager<H: ActivityHost> {
stack: Vec<Box<dyn Activity<H>>>,
factory: Box<dyn Fn(&H::Route) -> Box<dyn Activity<H>>>,
}
impl<H: ActivityHost> ActivityManager<H> {
pub fn new<F>(factory: F) -> Self
where
F: Fn(&H::Route) -> Box<dyn Activity<H>> + 'static,
{
log::info!("ActivityManager initialized.");
Self {
stack: Vec::new(),
factory: Box::new(factory),
}
}
pub fn update(&mut self, context: &mut H::Context, message: H::Message) -> H::Task {
if let Some(top) = self.stack.last_mut() {
log::trace!("Dispatching update to top activity: {:?}", message);
top.update(context, message)
} else {
log::warn!("Attempted to update but activity stack is empty.");
H::empty_task()
}
}
pub fn start_activity(&mut self, context: &mut H::Context, route: H::Route) -> H::Task {
let mode = route.launch_mode();
log::info!(
"Starting activity with route: {:?}, launch mode: {:?}",
route,
mode
);
match mode {
LaunchMode::Standard => self.push_activity(context, route),
LaunchMode::SingleTop => {
if let Some(top) = self.stack.last_mut() {
if top.route() == route {
log::debug!("SingleTop matched. Reusing top activity.");
return top.on_new_intent(context, route);
}
}
log::debug!("SingleTop not matched. Pushing as Standard.");
self.push_activity(context, route)
}
LaunchMode::SingleTask => {
let mut found_index = None;
for (i, act) in self.stack.iter().enumerate() {
if act.route() == route {
found_index = Some(i);
break;
}
}
if let Some(index) = found_index {
log::debug!("SingleTask matched at index {}. Clearing top.", index);
let mut tasks = Vec::new();
while self.stack.len() > index + 1 {
tasks.push(self.pop_internal(context));
}
let target = &mut self.stack[index];
tasks.push(target.on_new_intent(context, route));
tasks.push(target.on_resume(context));
H::batch_tasks(tasks)
} else {
log::debug!("SingleTask target not found in stack. Pushing new.");
self.push_activity(context, route)
}
}
LaunchMode::SingleInstance => {
log::debug!("SingleInstance mode. Clearing entire stack.");
let mut tasks = Vec::new();
while !self.stack.is_empty() {
tasks.push(self.pop_internal(context));
}
tasks.push(self.push_activity(context, route));
H::batch_tasks(tasks)
}
}
}
pub fn back(&mut self, context: &mut H::Context) -> (bool, H::Task) {
if self.stack.len() > 1 {
log::info!("Back navigation triggered. Popping top activity.");
let mut tasks = Vec::new();
tasks.push(self.pop_internal(context));
if let Some(top) = self.stack.last_mut() {
log::debug!("Resuming the new top activity.");
tasks.push(top.on_resume(context)); }
(true, H::batch_tasks(tasks))
} else {
log::info!("Back navigation ignored. Stack has 1 or fewer activities.");
(false, H::empty_task())
}
}
pub fn views<'a>(&'a self, context: &'a H::Context) -> Vec<H::View<'a>> {
log::trace!("Collecting views for rendering.");
let mut views = Vec::new();
let mut start_index = 0;
for (i, act) in self.stack.iter().enumerate().rev() {
if !act.route().is_translucent() {
start_index = i;
log::trace!(
"Found opaque activity at index {}. Stopping downward scan.",
i
);
break;
}
}
for i in start_index..self.stack.len() {
views.push(self.stack[i].view(context));
}
views
}
fn push_activity(&mut self, context: &mut H::Context, route: H::Route) -> H::Task {
log::debug!("Pushing new activity: {:?}", route);
let mut tasks = Vec::new();
if let Some(top) = self.stack.last_mut() {
tasks.push(top.on_pause(context));
}
let mut new_act = (self.factory)(&route);
tasks.push(new_act.on_create(context));
tasks.push(new_act.on_resume(context));
self.stack.push(new_act);
H::batch_tasks(tasks)
}
fn pop_internal(&mut self, context: &mut H::Context) -> H::Task {
if let Some(mut act) = self.stack.pop() {
log::debug!("Popping activity: {:?}", act.route());
let t1 = act.on_pause(context);
let t2 = act.on_destroy(context);
H::batch_tasks(vec![t1, t2])
} else {
H::empty_task()
}
}
pub fn stack_len(&self) -> usize {
self.stack.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, PartialEq)]
enum TestRoute {
StandardPage,
SingleTopPage,
SingleTaskPage,
SingleInstancePage,
TranslucentPage,
OpaquePage,
}
impl Route for TestRoute {
fn launch_mode(&self) -> LaunchMode {
match self {
TestRoute::StandardPage => LaunchMode::Standard,
TestRoute::SingleTopPage => LaunchMode::SingleTop,
TestRoute::SingleTaskPage => LaunchMode::SingleTask,
TestRoute::SingleInstancePage => LaunchMode::SingleInstance,
_ => LaunchMode::Standard,
}
}
fn is_translucent(&self) -> bool {
matches!(self, TestRoute::TranslucentPage)
}
}
struct TestHost;
impl ActivityHost for TestHost {
type Message = ();
type Context = Vec<String>; type Route = TestRoute;
type View<'a> = &'a str;
type Task = Vec<String>;
fn empty_task() -> Self::Task {
vec![]
}
fn batch_tasks(tasks: Vec<Self::Task>) -> Self::Task {
tasks.into_iter().flatten().collect()
}
}
struct TestActivity {
route: TestRoute,
name: String,
}
impl TestActivity {
fn new(route: TestRoute) -> Self {
let name = format!("{:?}", route);
Self { route, name }
}
}
impl Activity<TestHost> for TestActivity {
fn view<'a>(
&'a self,
_context: &'a <TestHost as ActivityHost>::Context,
) -> <TestHost as ActivityHost>::View<'a> {
&self.name
}
fn on_create(
&mut self,
context: &mut <TestHost as ActivityHost>::Context,
) -> <TestHost as ActivityHost>::Task {
context.push(format!("on_create: {}", self.name));
vec![format!("task_create_{}", self.name)]
}
fn on_resume(
&mut self,
context: &mut <TestHost as ActivityHost>::Context,
) -> <TestHost as ActivityHost>::Task {
context.push(format!("on_resume: {}", self.name));
vec![]
}
fn on_pause(
&mut self,
context: &mut <TestHost as ActivityHost>::Context,
) -> <TestHost as ActivityHost>::Task {
context.push(format!("on_pause: {}", self.name));
vec![]
}
fn on_destroy(
&mut self,
context: &mut <TestHost as ActivityHost>::Context,
) -> <TestHost as ActivityHost>::Task {
context.push(format!("on_destroy: {}", self.name));
vec![format!("task_destroy_{}", self.name)]
}
fn on_new_intent(
&mut self,
context: &mut <TestHost as ActivityHost>::Context,
_route: <TestHost as ActivityHost>::Route,
) -> <TestHost as ActivityHost>::Task {
context.push(format!("on_new_intent: {}", self.name));
vec![format!("task_new_intent_{}", self.name)]
}
fn route(&self) -> <TestHost as ActivityHost>::Route {
self.route.clone()
}
}
fn create_manager() -> ActivityManager<TestHost> {
ActivityManager::new(|route: &TestRoute| {
Box::new(TestActivity::new(route.clone())) as Box<dyn Activity<TestHost>>
})
}
#[test]
fn test_launch_mode_standard() {
let mut manager = create_manager();
let mut context = Vec::new();
manager.start_activity(&mut context, TestRoute::StandardPage);
manager.start_activity(&mut context, TestRoute::StandardPage);
assert_eq!(manager.stack_len(), 2);
assert_eq!(
context,
vec![
"on_create: StandardPage",
"on_resume: StandardPage",
"on_pause: StandardPage",
"on_create: StandardPage",
"on_resume: StandardPage"
]
);
}
#[test]
fn test_launch_mode_single_top() {
let mut manager = create_manager();
let mut context = Vec::new();
manager.start_activity(&mut context, TestRoute::StandardPage);
manager.start_activity(&mut context, TestRoute::SingleTopPage);
context.clear();
let task = manager.start_activity(&mut context, TestRoute::SingleTopPage);
assert_eq!(manager.stack_len(), 2);
assert_eq!(context, vec!["on_new_intent: SingleTopPage"]);
assert_eq!(task, vec!["task_new_intent_SingleTopPage"]);
}
#[test]
fn test_launch_mode_single_task() {
let mut manager = create_manager();
let mut context = Vec::new();
manager.start_activity(&mut context, TestRoute::StandardPage);
manager.start_activity(&mut context, TestRoute::SingleTaskPage); manager.start_activity(&mut context, TestRoute::StandardPage); manager.start_activity(&mut context, TestRoute::StandardPage);
assert_eq!(manager.stack_len(), 4);
context.clear();
let task = manager.start_activity(&mut context, TestRoute::SingleTaskPage);
assert_eq!(manager.stack_len(), 2);
assert_eq!(
context,
vec![
"on_pause: StandardPage", "on_destroy: StandardPage", "on_pause: StandardPage", "on_destroy: StandardPage", "on_new_intent: SingleTaskPage", "on_resume: SingleTaskPage"
]
);
assert_eq!(
task,
vec![
"task_destroy_StandardPage",
"task_destroy_StandardPage",
"task_new_intent_SingleTaskPage"
]
);
}
#[test]
fn test_launch_mode_single_instance() {
let mut manager = create_manager();
let mut context = Vec::new();
manager.start_activity(&mut context, TestRoute::StandardPage);
manager.start_activity(&mut context, TestRoute::StandardPage);
manager.start_activity(&mut context, TestRoute::SingleInstancePage);
assert_eq!(manager.stack_len(), 1);
assert_eq!(manager.stack[0].route(), TestRoute::SingleInstancePage);
}
#[test]
fn test_back_navigation() {
let mut manager = create_manager();
let mut context = Vec::new();
manager.start_activity(&mut context, TestRoute::StandardPage); manager.start_activity(&mut context, TestRoute::SingleTopPage); context.clear();
let (success, task) = manager.back(&mut context);
assert!(success);
assert_eq!(manager.stack_len(), 1);
assert_eq!(
context,
vec![
"on_pause: SingleTopPage",
"on_destroy: SingleTopPage",
"on_resume: StandardPage" ]
);
assert_eq!(task, vec!["task_destroy_SingleTopPage"]);
context.clear();
let (success, _) = manager.back(&mut context);
assert!(!success);
assert!(context.is_empty());
}
#[test]
fn test_views_painters_algorithm() {
let mut manager = create_manager();
let mut context = Vec::new();
manager.start_activity(&mut context, TestRoute::OpaquePage);
manager.start_activity(&mut context, TestRoute::TranslucentPage);
manager.start_activity(&mut context, TestRoute::TranslucentPage);
let views = manager.views(&context);
assert_eq!(views.len(), 3);
assert_eq!(
views,
vec!["OpaquePage", "TranslucentPage", "TranslucentPage"]
);
manager.start_activity(&mut context, TestRoute::OpaquePage);
let views_top = manager.views(&context);
assert_eq!(views_top.len(), 1);
assert_eq!(views_top, vec!["OpaquePage"]);
}
}