use log::{debug, info, trace, warn};
use std::fmt::Debug;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LaunchMode {
Standard,
SingleTop,
SingleTask,
SingleInstance,
}
pub trait Route: Debug + Clone + PartialEq + Send + Sync + 'static {
fn launch_mode(&self) -> LaunchMode;
fn is_translucent(&self) -> bool;
}
#[derive(Debug, Clone)]
pub struct Intent<R: Route> {
pub target: R,
}
impl<R: Route> Intent<R> {
pub fn new(target: R) -> Self {
Self { target }
}
}
pub trait ActivityHost: 'static {
type View;
type Effect;
type Subscription;
type Message: Clone + Debug + Send + Sync;
}
pub trait Activity<R, H, C>: 'static
where
R: Route,
H: ActivityHost,
C: Clone + Send + Sync,
{
fn route(&self) -> R;
fn update(&mut self, message: H::Message) -> Vec<H::Effect>;
fn view(&self) -> H::View;
fn subscription(&self) -> Option<H::Subscription> {
None
}
fn on_create(&mut self) -> Vec<H::Effect> {
vec![]
}
fn on_resume(&mut self) -> Vec<H::Effect> {
vec![]
}
fn on_pause(&mut self) -> Vec<H::Effect> {
vec![]
}
fn on_destroy(&mut self) -> Vec<H::Effect> {
vec![]
}
fn on_new_intent(&mut self, _intent: Intent<R>) -> Vec<H::Effect> {
vec![]
}
}
pub struct ActivityManager<R, H, C>
where
R: Route,
H: ActivityHost,
C: Clone + Send + Sync + 'static,
{
stack: Vec<Box<dyn Activity<R, H, C>>>,
context: C,
factory: Box<dyn Fn(&R, &C) -> Box<dyn Activity<R, H, C>>>,
}
impl<R, H, C> ActivityManager<R, H, C>
where
R: Route,
H: ActivityHost,
C: Clone + Send + Sync + 'static,
{
pub fn new(
initial_route: R,
context: C,
factory: Box<dyn Fn(&R, &C) -> Box<dyn Activity<R, H, C>>>,
) -> (Self, Vec<H::Effect>) {
info!(
"ActivityManager initialized. Starting initial route: {:?}",
initial_route
);
let mut manager = Self {
stack: Vec::new(),
context,
factory,
};
let effects = manager.start_activity(Intent::new(initial_route));
(manager, effects)
}
pub fn start_activity(&mut self, intent: Intent<R>) -> Vec<H::Effect> {
let target = intent.target.clone();
let mode = target.launch_mode();
info!(
"Action: start_activity | Target: {:?} | Mode: {:?}",
target, mode
);
let mut effects = Vec::new();
let existing_index = self.stack.iter().position(|a| a.route() == target);
match mode {
LaunchMode::SingleInstance => {
if existing_index.is_some() {
debug!(
"SingleInstance trigger: Route {:?} exists. Clearing others.",
target
);
while let Some(top) = self.stack.last() {
if top.route() == target {
break;
}
let mut old = self.stack.pop().unwrap();
debug!("Popping higher activity: {:?}", old.route());
effects.extend(old.on_pause());
effects.extend(old.on_destroy());
}
if let Some(mut top) = self.stack.pop() {
if top.route() == target {
while let Some(mut bottom) = self.stack.pop() {
debug!("Popping lower activity: {:?}", bottom.route());
effects.extend(bottom.on_pause());
effects.extend(bottom.on_destroy());
}
effects.extend(top.on_new_intent(intent));
effects.extend(top.on_resume());
self.stack.push(top);
return effects; } else {
self.stack.push(top); }
}
} else {
debug!("SingleInstance trigger: New instance. Clearing entire stack.");
while let Some(mut old) = self.stack.pop() {
effects.extend(old.on_pause());
effects.extend(old.on_destroy());
}
}
}
LaunchMode::SingleTask => {
if let Some(index) = existing_index {
debug!(
"SingleTask trigger: Route {:?} found at index {}. Clearing top.",
target, index
);
while self.stack.len() > index + 1 {
if let Some(mut old) = self.stack.pop() {
effects.extend(old.on_pause());
effects.extend(old.on_destroy());
}
}
if let Some(top) = self.stack.last_mut() {
effects.extend(top.on_new_intent(intent));
effects.extend(top.on_resume());
}
return effects; }
}
LaunchMode::SingleTop => {
if let Some(top) = self.stack.last_mut() {
if top.route() == target {
debug!(
"SingleTop trigger: Route {:?} is already at top. Reusing.",
target
);
effects.extend(top.on_new_intent(intent));
effects.extend(top.on_resume());
return effects; }
}
}
LaunchMode::Standard => {
trace!("Standard trigger: Proceeding to create new instance.");
}
}
if let Some(top) = self.stack.last_mut() {
debug!("Pausing current top activity: {:?}", top.route());
effects.extend(top.on_pause());
}
debug!("Instantiating new activity for route: {:?}", target);
let mut new_activity = (self.factory)(&target, &self.context);
effects.extend(new_activity.on_create());
effects.extend(new_activity.on_resume());
self.stack.push(new_activity);
effects
}
pub fn back(&mut self) -> Vec<H::Effect> {
info!("Action: back | Stack depth before: {}", self.stack.len());
if self.stack.len() > 1 {
let mut effects = Vec::new();
if let Some(mut old) = self.stack.pop() {
debug!("Destroying top activity: {:?}", old.route());
effects.extend(old.on_pause());
effects.extend(old.on_destroy());
}
if let Some(new_top) = self.stack.last_mut() {
debug!("Resuming previous activity: {:?}", new_top.route());
effects.extend(new_top.on_resume());
}
effects
} else {
warn!("Back action ignored: Root activity cannot be popped via framework back().");
vec![]
}
}
pub fn update(&mut self, message: H::Message) -> Vec<H::Effect> {
if let Some(top) = self.stack.last_mut() {
trace!("Routing message to top activity: {:?}", top.route());
top.update(message)
} else {
vec![]
}
}
pub fn views(&self) -> Vec<H::View> {
if self.stack.is_empty() {
return vec![];
}
let base_index = self
.stack
.iter()
.rposition(|a| !a.route().is_translucent())
.unwrap_or(0);
self.stack[base_index..].iter().map(|a| a.view()).collect()
}
pub fn subscriptions(&self) -> Vec<H::Subscription> {
if self.stack.is_empty() {
return vec![];
}
let base_index = self
.stack
.iter()
.rposition(|a| !a.route().is_translucent())
.unwrap_or(0);
self.stack[base_index..]
.iter()
.filter_map(|a| a.subscription())
.collect()
}
#[cfg(test)]
pub fn stack_len(&self) -> usize {
self.stack.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, PartialEq)]
enum AppRoute {
Home,
Settings,
Detail(u32),
Dialog,
}
impl Route for AppRoute {
fn launch_mode(&self) -> LaunchMode {
match self {
AppRoute::Home => LaunchMode::SingleTask,
AppRoute::Settings => LaunchMode::SingleInstance,
AppRoute::Detail(_) => LaunchMode::Standard,
AppRoute::Dialog => LaunchMode::SingleTop,
}
}
fn is_translucent(&self) -> bool {
matches!(self, AppRoute::Dialog)
}
}
struct MockHost;
impl ActivityHost for MockHost {
type View = String;
type Effect = ();
type Subscription = ();
type Message = String;
}
#[derive(Clone)]
struct AppContext {
history: Arc<Mutex<Vec<String>>>,
}
impl AppContext {
fn new() -> Self {
Self {
history: Arc::new(Mutex::new(Vec::new())),
}
}
fn record(&self, msg: &str) {
self.history.lock().unwrap().push(msg.to_string());
}
fn take_history(&self) -> Vec<String> {
let mut guard = self.history.lock().unwrap();
let res = guard.clone();
guard.clear();
res
}
}
struct MockActivity {
route: AppRoute,
context: AppContext,
}
impl Activity<AppRoute, MockHost, AppContext> for MockActivity {
fn route(&self) -> AppRoute {
self.route.clone()
}
fn view(&self) -> <MockHost as ActivityHost>::View {
format!("View:{:?}", self.route)
}
fn update(&mut self, _msg: String) -> Vec<()> {
vec![]
}
fn on_create(&mut self) -> Vec<()> {
self.context.record(&format!("{:?} onCreate", self.route));
vec![]
}
fn on_resume(&mut self) -> Vec<()> {
self.context.record(&format!("{:?} onResume", self.route));
vec![]
}
fn on_pause(&mut self) -> Vec<()> {
self.context.record(&format!("{:?} onPause", self.route));
vec![]
}
fn on_destroy(&mut self) -> Vec<()> {
self.context.record(&format!("{:?} onDestroy", self.route));
vec![]
}
fn on_new_intent(&mut self, _intent: Intent<AppRoute>) -> Vec<()> {
self.context
.record(&format!("{:?} onNewIntent", self.route));
vec![]
}
}
fn setup_manager() -> (ActivityManager<AppRoute, MockHost, AppContext>, AppContext) {
let ctx = AppContext::new();
let factory: Box<
dyn Fn(&AppRoute, &AppContext) -> Box<dyn Activity<AppRoute, MockHost, AppContext>>,
> = Box::new(|r, c| {
Box::new(MockActivity {
route: r.clone(),
context: c.clone(),
})
});
let (manager, _) = ActivityManager::new(AppRoute::Home, ctx.clone(), factory);
(manager, ctx)
}
#[test]
fn test_initialization_lifecycle() {
let (_, ctx) = setup_manager();
assert_eq!(
ctx.take_history(),
vec!["Home onCreate", "Home onResume"],
"初始路由必须触发 onCreate 和 onResume"
);
}
#[test]
fn test_standard_launch_mode() {
let (mut manager, ctx) = setup_manager();
ctx.take_history();
manager.start_activity(Intent::new(AppRoute::Detail(1)));
assert_eq!(
ctx.take_history(),
vec!["Home onPause", "Detail(1) onCreate", "Detail(1) onResume"]
);
manager.start_activity(Intent::new(AppRoute::Detail(1)));
assert_eq!(
ctx.take_history(),
vec![
"Detail(1) onPause",
"Detail(1) onCreate",
"Detail(1) onResume"
]
);
assert_eq!(manager.stack_len(), 3); }
#[test]
fn test_single_top_launch_mode() {
let (mut manager, ctx) = setup_manager();
ctx.take_history();
manager.start_activity(Intent::new(AppRoute::Dialog));
assert_eq!(
ctx.take_history(),
vec!["Home onPause", "Dialog onCreate", "Dialog onResume"]
);
manager.start_activity(Intent::new(AppRoute::Dialog));
assert_eq!(
ctx.take_history(),
vec!["Dialog onNewIntent", "Dialog onResume"]
);
assert_eq!(manager.stack_len(), 2);
manager.start_activity(Intent::new(AppRoute::Detail(1)));
ctx.take_history();
manager.start_activity(Intent::new(AppRoute::Dialog));
assert_eq!(
ctx.take_history(),
vec!["Detail(1) onPause", "Dialog onCreate", "Dialog onResume"]
);
assert_eq!(manager.stack_len(), 4); }
#[test]
fn test_single_task_launch_mode() {
let (mut manager, ctx) = setup_manager(); manager.start_activity(Intent::new(AppRoute::Detail(1)));
manager.start_activity(Intent::new(AppRoute::Detail(2)));
ctx.take_history();
assert_eq!(manager.stack_len(), 3);
manager.start_activity(Intent::new(AppRoute::Home));
assert_eq!(
ctx.take_history(),
vec![
"Detail(2) onPause",
"Detail(2) onDestroy",
"Detail(1) onPause",
"Detail(1) onDestroy",
"Home onNewIntent",
"Home onResume"
]
);
assert_eq!(manager.stack_len(), 1); }
#[test]
fn test_single_instance_launch_mode() {
let (mut manager, ctx) = setup_manager();
manager.start_activity(Intent::new(AppRoute::Settings)); assert_eq!(manager.stack_len(), 1);
let history = ctx.take_history();
assert!(history.contains(&"Home onPause".to_string()));
assert!(history.contains(&"Home onDestroy".to_string()));
assert!(history.contains(&"Settings onCreate".to_string()));
assert!(history.contains(&"Settings onResume".to_string()));
manager.start_activity(Intent::new(AppRoute::Detail(1)));
ctx.take_history();
manager.start_activity(Intent::new(AppRoute::Settings));
let clear_history = ctx.take_history();
assert_eq!(
clear_history,
vec![
"Detail(1) onPause",
"Detail(1) onDestroy",
"Settings onNewIntent",
"Settings onResume"
]
);
assert_eq!(manager.stack_len(), 1);
}
#[test]
fn test_back_navigation() {
let (mut manager, ctx) = setup_manager();
manager.start_activity(Intent::new(AppRoute::Detail(1)));
ctx.take_history();
manager.back();
assert_eq!(
ctx.take_history(),
vec!["Detail(1) onPause", "Detail(1) onDestroy", "Home onResume"]
);
assert_eq!(manager.stack_len(), 1);
manager.back();
assert!(ctx.take_history().is_empty());
assert_eq!(manager.stack_len(), 1);
}
#[test]
fn test_translucent_view_rendering() {
let (mut manager, _ctx) = setup_manager();
assert_eq!(manager.views(), vec!["View:Home"]);
manager.start_activity(Intent::new(AppRoute::Detail(1)));
assert_eq!(manager.views(), vec!["View:Detail(1)"]);
manager.start_activity(Intent::new(AppRoute::Dialog));
assert_eq!(manager.views(), vec!["View:Detail(1)", "View:Dialog"]);
manager.start_activity(Intent::new(AppRoute::Dialog)); assert_eq!(manager.views(), vec!["View:Detail(1)", "View:Dialog"]);
struct StandardDialogMockRoute;
}
}