diff --git a/src/app.rs b/src/app.rs index 97d3eefbc..23b447b14 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,7 +3,7 @@ use crate::database::Database; use crate::logging::initialize_logger; use crate::platform::{BackendTask, BackendTaskSuccessResult}; use crate::ui::document_query_screen::DocumentQueryScreen; -use crate::ui::dpns_contested_names_screen::DPNSContestedNamesScreen; +use crate::ui::dpns_contested_names_screen::{DPNSContestedNamesScreen, DPNSSubscreen}; use crate::ui::identities::identities_screen::IdentitiesScreen; use crate::ui::network_chooser_screen::NetworkChooserScreen; use crate::ui::transition_visualizer_screen::TransitionVisualizerScreen; @@ -116,7 +116,12 @@ impl AppState { let testnet_app_context = AppContext::new(Network::Testnet, db.clone()); let mut identities_screen = IdentitiesScreen::new(&mainnet_app_context); - let mut dpns_contested_names_screen = DPNSContestedNamesScreen::new(&mainnet_app_context); + let mut dpns_active_contests_screen = + DPNSContestedNamesScreen::new(&mainnet_app_context, DPNSSubscreen::Active); + let mut dpns_past_contests_screen = + DPNSContestedNamesScreen::new(&mainnet_app_context, DPNSSubscreen::Past); + let mut dpns_my_usernames_screen = + DPNSContestedNamesScreen::new(&mainnet_app_context, DPNSSubscreen::Owned); let mut transition_visualizer_screen = TransitionVisualizerScreen::new(&mainnet_app_context); let mut document_query_screen = DocumentQueryScreen::new(&mainnet_app_context); @@ -136,7 +141,12 @@ impl AppState { if network == Network::Testnet && testnet_app_context.is_some() { let testnet_app_context = testnet_app_context.as_ref().unwrap(); identities_screen = IdentitiesScreen::new(testnet_app_context); - dpns_contested_names_screen = DPNSContestedNamesScreen::new(testnet_app_context); + dpns_active_contests_screen = + DPNSContestedNamesScreen::new(&testnet_app_context, DPNSSubscreen::Active); + dpns_past_contests_screen = + DPNSContestedNamesScreen::new(&testnet_app_context, DPNSSubscreen::Past); + dpns_my_usernames_screen = + DPNSContestedNamesScreen::new(&testnet_app_context, DPNSSubscreen::Owned); transition_visualizer_screen = TransitionVisualizerScreen::new(testnet_app_context); document_query_screen = DocumentQueryScreen::new(testnet_app_context); } @@ -156,8 +166,16 @@ impl AppState { Screen::IdentitiesScreen(identities_screen), ), ( - RootScreenType::RootScreenDPNSContestedNames, - Screen::DPNSContestedNamesScreen(dpns_contested_names_screen), + RootScreenType::RootScreenDPNSActiveContests, + Screen::DPNSContestedNamesScreen(dpns_active_contests_screen), + ), + ( + RootScreenType::RootScreenDPNSPastContests, + Screen::DPNSContestedNamesScreen(dpns_past_contests_screen), + ), + ( + RootScreenType::RootScreenDPNSOwnedNames, + Screen::DPNSContestedNamesScreen(dpns_my_usernames_screen), ), ( RootScreenType::RootScreenTransitionVisualizerScreen, diff --git a/src/context.rs b/src/context.rs index 22c0f83c8..bda341a94 100644 --- a/src/context.rs +++ b/src/context.rs @@ -9,6 +9,7 @@ use crate::sdk_wrapper::initialize_sdk; use crate::ui::RootScreenType; use dash_sdk::dashcore_rpc::{Auth, Client}; use dash_sdk::dpp::dashcore::Network; +use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::identity::Identity; use dash_sdk::dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; use dash_sdk::dpp::version::PlatformVersion; @@ -130,6 +131,34 @@ impl AppContext { self.db.get_ongoing_contested_names(self) } + pub fn owned_contested_names(&self) -> Result> { + let all_contested_names = self.all_contested_names().unwrap_or_else(|e| { + tracing::error!("Failed to load contested names: {:?}", e); + Vec::new() // Use default value if loading fails + }); + let local_qualified_identities = + self.load_local_qualified_identities().unwrap_or_else(|e| { + tracing::error!("Failed to load local qualified identities: {:?}", e); + Vec::new() // Use default value if loading fails + }); + + let owned_contested_names = all_contested_names + .into_iter() + .filter(|contested_name| { + contested_name + .awarded_to + .as_ref() + .map_or(false, |awarded_to| { + local_qualified_identities + .iter() + .any(|identity| identity.identity.id() == awarded_to) + }) + }) + .collect(); + + Ok(owned_contested_names) + } + /// Updates the `start_root_screen` in the settings table pub fn update_settings(&self, root_screen_type: RootScreenType) -> Result<()> { self.db diff --git a/src/main.rs b/src/main.rs index c7b0e4708..15f757132 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,7 @@ fn main() -> eframe::Result<()> { ..Default::default() }; eframe::run_native( - "Identity Manager", + "Dash Evo Tool", native_options, Box::new(|_cc| Ok(Box::new(app::AppState::new()))), ) diff --git a/src/ui/components/dpns_subscreen_chooser_panel.rs b/src/ui/components/dpns_subscreen_chooser_panel.rs new file mode 100644 index 000000000..e50bfbde5 --- /dev/null +++ b/src/ui/components/dpns_subscreen_chooser_panel.rs @@ -0,0 +1,57 @@ +use crate::ui::dpns_contested_names_screen::DPNSSubscreen; +use crate::{app::AppAction, ui::RootScreenType}; +use egui::{Context, Frame, Margin, SidePanel}; + +pub fn add_dpns_subscreen_chooser_panel(ctx: &Context) -> AppAction { + let mut action = AppAction::None; + + let subscreens = vec![ + DPNSSubscreen::Active, + DPNSSubscreen::Past, + DPNSSubscreen::Owned, + ]; + + SidePanel::left("dpns_subscreen_chooser_panel") + .default_width(250.0) + .frame( + Frame::none() + .fill(ctx.style().visuals.panel_fill) + .inner_margin(Margin::same(10.0)), + ) + .show(ctx, |ui| { + // Display subscreen names + ui.vertical(|ui| { + ui.label("DPNS Subscreens"); + ui.add_space(10.0); + + for subscreen in subscreens { + // Show the subscreen name as a clickable option + if ui.button(subscreen.display_name()).clicked() { + // Handle navigation based on which subscreen is selected + match subscreen { + DPNSSubscreen::Active => { + action = AppAction::SetMainScreen( + RootScreenType::RootScreenDPNSActiveContests, + ) + } + DPNSSubscreen::Past => { + action = AppAction::SetMainScreen( + RootScreenType::RootScreenDPNSPastContests, + ) + } + DPNSSubscreen::Owned => { + action = AppAction::SetMainScreen( + RootScreenType::RootScreenDPNSOwnedNames, + ) + } + _ => {} + } + } + + ui.add_space(5.0); + } + }); + }); + + action +} diff --git a/src/ui/components/left_panel.rs b/src/ui/components/left_panel.rs index bd171d24d..cb9fdcef0 100644 --- a/src/ui/components/left_panel.rs +++ b/src/ui/components/left_panel.rs @@ -47,7 +47,7 @@ pub fn add_left_panel( ("I", RootScreenType::RootScreenIdentities, "identity.png"), ( "C", - RootScreenType::RootScreenDPNSContestedNames, + RootScreenType::RootScreenDPNSActiveContests, "voting.png", ), ("Q", RootScreenType::RootScreenDocumentQuery, "doc.png"), diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs index 37b84c0c5..e8e64de30 100644 --- a/src/ui/components/mod.rs +++ b/src/ui/components/mod.rs @@ -1,4 +1,5 @@ pub mod contract_chooser_panel; +pub mod dpns_subscreen_chooser_panel; pub mod entropy_grid; pub mod left_panel; pub mod top_panel; diff --git a/src/ui/dpns_contested_names_screen.rs b/src/ui/dpns_contested_names_screen.rs index 9ac873555..55b58469b 100644 --- a/src/ui/dpns_contested_names_screen.rs +++ b/src/ui/dpns_contested_names_screen.rs @@ -1,7 +1,8 @@ +use super::components::dpns_subscreen_chooser_panel::add_dpns_subscreen_chooser_panel; use super::{Screen, ScreenType}; use crate::app::{AppAction, DesiredAppAction}; use crate::context::AppContext; -use crate::model::contested_name::ContestedName; +use crate::model::contested_name::{ContestState, ContestedName}; use crate::model::qualified_identity::{IdentityType, QualifiedIdentity}; use crate::platform::contested_names::ContestedResourceTask; use crate::platform::BackendTask; @@ -11,6 +12,7 @@ use crate::ui::identities::add_existing_identity_screen::AddExistingIdentityScre use crate::ui::{MessageType, RootScreenType, ScreenLike}; use chrono::{DateTime, LocalResult, TimeZone, Utc}; use chrono_humanize::HumanTime; +use dash_sdk::dpp::platform_value::string_encoding::Encoding; use dash_sdk::dpp::voting::vote_choices::resource_vote_choice::ResourceVoteChoice; use egui::{Context, Frame, Margin, Ui}; use egui_extras::{Column, TableBuilder}; @@ -24,6 +26,7 @@ enum SortColumn { AbstainVotes, EndingTime, LastUpdated, + AwardedTo, } #[derive(Clone, Copy, PartialEq, Eq)] @@ -32,6 +35,22 @@ enum SortOrder { Descending, } +pub enum DPNSSubscreen { + Active, + Past, + Owned, +} + +impl DPNSSubscreen { + pub fn display_name(&self) -> &'static str { + match self { + Self::Active => "Active contests", + Self::Past => "Past contests", + Self::Owned => "My usernames", + } + } +} + pub struct DPNSContestedNamesScreen { // No need for Mutex as this can only refresh when entering screen voting_identities: Arc>, @@ -42,16 +61,32 @@ pub struct DPNSContestedNamesScreen { sort_column: SortColumn, sort_order: SortOrder, show_vote_popup_info: Option<(String, ContestedResourceTask)>, + pub dpns_subscreen: DPNSSubscreen, } impl DPNSContestedNamesScreen { - pub fn new(app_context: &Arc) -> Self { - let contested_names = Arc::new(Mutex::new( - app_context.ongoing_contested_names().unwrap_or_else(|e| { - error!("Failed to load contested names: {:?}", e); - Vec::new() // Use default value if loading fails - }), - )); + pub fn new(app_context: &Arc, dpns_subscreen: DPNSSubscreen) -> Self { + let contested_names = Arc::new(Mutex::new(match dpns_subscreen { + DPNSSubscreen::Active => { + app_context.ongoing_contested_names().unwrap_or_else(|e| { + error!("Failed to load contested names: {:?}", e); + Vec::new() // Use default value if loading fails + }) + } + DPNSSubscreen::Past => { + app_context.all_contested_names().unwrap_or_else(|e| { + error!("Failed to load contested names: {:?}", e); + Vec::new() // Use default value if loading fails + }) + } + DPNSSubscreen::Owned => { + app_context.owned_contested_names().unwrap_or_else(|e| { + error!("Failed to load contested names: {:?}", e); + Vec::new() // Use default value if loading fails + }) + } + })); + let voting_identities = app_context .db .get_local_voting_identities(&app_context) @@ -69,6 +104,7 @@ impl DPNSContestedNamesScreen { sort_column: SortColumn::ContestedName, sort_order: SortOrder::Ascending, show_vote_popup_info: None, + dpns_subscreen, } } @@ -81,7 +117,14 @@ impl DPNSContestedNamesScreen { ) { if let Some(contestants) = &contested_name.contestants { for contestant in contestants { - let button_text = format!("{} - {} votes", contestant.name, contestant.votes); + let first_6_chars_of_id: String = contestant + .id + .to_string(Encoding::Base58) + .chars() + .take(6) + .collect(); + let button_text = + format!("{}... - {} votes", first_6_chars_of_id, contestant.votes); // Determine if this contestant's votes should be bold let text = if contestant.votes == max_contestant_votes && !is_locked_votes_bold { @@ -118,6 +161,7 @@ impl DPNSContestedNamesScreen { SortColumn::AbstainVotes => a.abstain_votes.cmp(&b.abstain_votes), SortColumn::EndingTime => a.end_time.cmp(&b.end_time), SortColumn::LastUpdated => a.last_updated.cmp(&b.last_updated), + SortColumn::AwardedTo => a.awarded_to.cmp(&b.awarded_to), }; if self.sort_order == SortOrder::Descending { @@ -159,12 +203,32 @@ impl DPNSContestedNamesScreen { fn render_no_active_contests(&mut self, ui: &mut Ui) { ui.vertical_centered(|ui| { ui.add_space(20.0); // Add some space to separate from the top - ui.label( - egui::RichText::new("No active contests at the moment.") - .heading() - .strong() - .color(egui::Color32::GRAY), - ); + match self.dpns_subscreen { + DPNSSubscreen::Active => { + ui.label( + egui::RichText::new("No active contests at the moment.") + .heading() + .strong() + .color(egui::Color32::GRAY), + ); + } + DPNSSubscreen::Past => { + ui.label( + egui::RichText::new("No active or past contests at the moment.") + .heading() + .strong() + .color(egui::Color32::GRAY), + ); + } + DPNSSubscreen::Owned => { + ui.label( + egui::RichText::new("No owned usernames.") + .heading() + .strong() + .color(egui::Color32::GRAY), + ); + } + } ui.add_space(10.0); ui.label("Please check back later or try refreshing the list."); ui.add_space(20.0); @@ -174,7 +238,7 @@ impl DPNSContestedNamesScreen { }); } - fn render_table(&mut self, ui: &mut Ui) { + fn render_table_active_contests(&mut self, ui: &mut Ui) { // Clone the contested names vector to avoid holding the lock during UI rendering let contested_names = { let contested_names_guard = self.contested_names.lock().unwrap(); @@ -350,6 +414,134 @@ impl DPNSContestedNamesScreen { }); } + fn render_table_past_contests(&mut self, ui: &mut Ui) { + // Clone the contested names vector to avoid holding the lock during UI rendering + let contested_names = { + let contested_names_guard = self.contested_names.lock().unwrap(); + let mut contested_names = contested_names_guard.clone(); + self.sort_contested_names(&mut contested_names); + contested_names + }; + + egui::ScrollArea::vertical().show(ui, |ui| { + Frame::group(ui.style()) + .fill(ui.visuals().panel_fill) + .stroke(egui::Stroke::new( + 1.0, + ui.visuals().widgets.inactive.bg_stroke.color, + )) + .inner_margin(Margin::same(8.0)) + .show(ui, |ui| { + TableBuilder::new(ui) + .striped(true) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .column(Column::initial(200.0).resizable(true)) // Contested Name + .column(Column::initial(200.0).resizable(true)) // Ended Time + .column(Column::initial(200.0).resizable(true)) // Last Updated + .column(Column::initial(200.0).resizable(true)) // Awarded To + .header(30.0, |mut header| { + header.col(|ui| { + if ui.button("Contested Name").clicked() { + self.toggle_sort(SortColumn::ContestedName); + } + }); + header.col(|ui| { + if ui.button("Ended Time").clicked() { + self.toggle_sort(SortColumn::EndingTime); + } + }); + header.col(|ui| { + if ui.button("Last Updated").clicked() { + self.toggle_sort(SortColumn::LastUpdated); + } + }); + header.col(|ui| { + if ui.button("Awarded To").clicked() { + self.toggle_sort(SortColumn::AwardedTo); + } + }); + }) + .body(|mut body| { + for contested_name in &contested_names { + body.row(25.0, |mut row| { + row.col(|ui| { + ui.label(&contested_name.normalized_contested_name); + }); + row.col(|ui| { + if let Some(ended_time) = contested_name.end_time { + // Convert the timestamp to a DateTime object using timestamp_millis_opt + if let LocalResult::Single(datetime) = + Utc.timestamp_millis_opt(ended_time as i64) + { + // Format the ISO date up to seconds + let iso_date = datetime + .format("%Y-%m-%d %H:%M:%S") + .to_string(); + + // Use chrono-humanize to get the relative time + let relative_time = + HumanTime::from(datetime).to_string(); + + // Combine both the ISO date and relative time + let display_text = + format!("{} ({})", iso_date, relative_time); + + ui.label(display_text); + } else { + // Handle case where the timestamp is invalid + ui.label("Invalid timestamp"); + } + } else { + ui.label("Fetching"); + } + }); + row.col(|ui| { + if let Some(last_updated) = contested_name.last_updated { + // Convert the timestamp to a DateTime object using timestamp_millis_opt + if let LocalResult::Single(datetime) = + Utc.timestamp_opt(last_updated as i64, 0) + { + // Use chrono-humanize to get the relative time + let relative_time = + HumanTime::from(datetime).to_string(); + + ui.label(relative_time); + } else { + // Handle case where the timestamp is invalid + ui.label("Invalid timestamp"); + } + } else { + ui.label("Fetching"); + } + }); + row.col(|ui| match contested_name.state { + ContestState::Unknown => { + ui.label("Fetching"); + } + ContestState::Joinable => { + ui.label("Active"); + } + ContestState::Ongoing => { + ui.label("Active"); + } + ContestState::WonBy(identifier) => { + ui.label(format!( + "{}", + identifier.to_string(Encoding::Base58), + )); + } + ContestState::Locked => { + ui.label("Locked"); + } + }); + }); + } + }); + }); + }); + } + fn show_vote_popup(&mut self, ui: &mut Ui) -> AppAction { let mut app_action = AppAction::None; if self.voting_identities.is_empty() { @@ -430,10 +622,20 @@ impl DPNSContestedNamesScreen { impl ScreenLike for DPNSContestedNamesScreen { fn refresh(&mut self) { let mut contested_names = self.contested_names.lock().unwrap(); - *contested_names = self - .app_context - .ongoing_contested_names() - .unwrap_or_default(); + match self.dpns_subscreen { + DPNSSubscreen::Active => { + *contested_names = self + .app_context + .ongoing_contested_names() + .unwrap_or_default(); + } + DPNSSubscreen::Past => { + *contested_names = self.app_context.all_contested_names().unwrap_or_default(); + } + DPNSSubscreen::Owned => { + *contested_names = self.app_context.owned_contested_names().unwrap_or_default(); + } + } } fn refresh_on_arrival(&mut self) { @@ -483,11 +685,30 @@ impl ScreenLike for DPNSContestedNamesScreen { right_buttons, ); - action |= add_left_panel( - ctx, - &self.app_context, - RootScreenType::RootScreenDPNSContestedNames, - ); + match self.dpns_subscreen { + DPNSSubscreen::Active => { + action |= add_left_panel( + ctx, + &self.app_context, + RootScreenType::RootScreenDPNSActiveContests, + ); + } + DPNSSubscreen::Past => { + action |= add_left_panel( + ctx, + &self.app_context, + RootScreenType::RootScreenDPNSPastContests, + ); + } + DPNSSubscreen::Owned => { + action |= add_left_panel( + ctx, + &self.app_context, + RootScreenType::RootScreenDPNSOwnedNames, + ); + } + } + action |= add_dpns_subscreen_chooser_panel(ctx); // Render the UI with the cloned contested_names vector egui::CentralPanel::default().show(ctx, |ui| { @@ -534,7 +755,17 @@ impl ScreenLike for DPNSContestedNamesScreen { if has_contested_names { // Render the table if there are contested names - self.render_table(ui); + match self.dpns_subscreen { + DPNSSubscreen::Active => { + self.render_table_active_contests(ui); + } + DPNSSubscreen::Past => { + self.render_table_past_contests(ui); + } + DPNSSubscreen::Owned => { + self.render_table_past_contests(ui); + } + } } else { // Render the "no active contests" message if none exist self.render_no_active_contests(ui); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index fb9712454..1d1cf8ef8 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -14,6 +14,7 @@ use crate::ui::transition_visualizer_screen::TransitionVisualizerScreen; use crate::ui::withdrawals::WithdrawalScreen; use dash_sdk::dpp::identity::Identity; use dash_sdk::dpp::prelude::IdentityPublicKey; +use dpns_contested_names_screen::DPNSSubscreen; use egui::Context; use enum_dispatch::enum_dispatch; use identities::add_existing_identity_screen::AddExistingIdentityScreen; @@ -39,7 +40,9 @@ pub mod withdrawals; #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum RootScreenType { RootScreenIdentities, - RootScreenDPNSContestedNames, + RootScreenDPNSActiveContests, + RootScreenDPNSPastContests, + RootScreenDPNSOwnedNames, RootScreenDocumentQuery, RootScreenTransitionVisualizerScreen, RootScreenNetworkChooser, @@ -50,10 +53,12 @@ impl RootScreenType { pub fn to_int(self) -> u32 { match self { RootScreenType::RootScreenIdentities => 0, - RootScreenType::RootScreenDPNSContestedNames => 1, + RootScreenType::RootScreenDPNSActiveContests => 1, RootScreenType::RootScreenDocumentQuery => 2, RootScreenType::RootScreenTransitionVisualizerScreen => 3, RootScreenType::RootScreenNetworkChooser => 4, + RootScreenType::RootScreenDPNSPastContests => 5, + RootScreenType::RootScreenDPNSOwnedNames => 6, } } @@ -61,10 +66,12 @@ impl RootScreenType { pub fn from_int(value: u32) -> Option { match value { 0 => Some(RootScreenType::RootScreenIdentities), - 1 => Some(RootScreenType::RootScreenDPNSContestedNames), + 1 => Some(RootScreenType::RootScreenDPNSActiveContests), 2 => Some(RootScreenType::RootScreenDocumentQuery), 3 => Some(RootScreenType::RootScreenTransitionVisualizerScreen), 4 => Some(RootScreenType::RootScreenNetworkChooser), + 5 => Some(RootScreenType::RootScreenDPNSPastContests), + 6 => Some(RootScreenType::RootScreenDPNSOwnedNames), _ => None, } } @@ -74,7 +81,9 @@ impl From for ScreenType { fn from(value: RootScreenType) -> Self { match value { RootScreenType::RootScreenIdentities => ScreenType::Identities, - RootScreenType::RootScreenDPNSContestedNames => ScreenType::DPNSContestedNames, + RootScreenType::RootScreenDPNSActiveContests => ScreenType::DPNSActiveContests, + RootScreenType::RootScreenDPNSPastContests => ScreenType::DPNSPastContests, + RootScreenType::RootScreenDPNSOwnedNames => ScreenType::DPNSMyUsernames, RootScreenType::RootScreenTransitionVisualizerScreen => { ScreenType::TransitionVisualizer } @@ -88,7 +97,9 @@ impl From for ScreenType { pub enum ScreenType { #[default] Identities, - DPNSContestedNames, + DPNSActiveContests, + DPNSPastContests, + DPNSMyUsernames, AddNewIdentity, AddNewWallet, AddExistingIdentity, @@ -107,9 +118,15 @@ impl ScreenType { pub fn create_screen(&self, app_context: &Arc) -> Screen { match self { ScreenType::Identities => Screen::IdentitiesScreen(IdentitiesScreen::new(app_context)), - ScreenType::DPNSContestedNames => { - Screen::DPNSContestedNamesScreen(DPNSContestedNamesScreen::new(app_context)) - } + ScreenType::DPNSActiveContests => Screen::DPNSContestedNamesScreen( + DPNSContestedNamesScreen::new(app_context, DPNSSubscreen::Active), + ), + ScreenType::DPNSPastContests => Screen::DPNSContestedNamesScreen( + DPNSContestedNamesScreen::new(app_context, DPNSSubscreen::Past), + ), + ScreenType::DPNSMyUsernames => Screen::DPNSContestedNamesScreen( + DPNSContestedNamesScreen::new(app_context, DPNSSubscreen::Owned), + ), ScreenType::AddNewIdentity => { Screen::AddNewIdentityScreen(AddNewIdentityScreen::new(app_context)) } @@ -241,7 +258,18 @@ impl Screen { screen.private_key_bytes.clone(), ), Screen::IdentitiesScreen(_) => ScreenType::Identities, - Screen::DPNSContestedNamesScreen(_) => ScreenType::DPNSContestedNames, + Screen::DPNSContestedNamesScreen(DPNSContestedNamesScreen { + dpns_subscreen: DPNSSubscreen::Active, + .. + }) => ScreenType::DPNSActiveContests, + Screen::DPNSContestedNamesScreen(DPNSContestedNamesScreen { + dpns_subscreen: DPNSSubscreen::Past, + .. + }) => ScreenType::DPNSPastContests, + Screen::DPNSContestedNamesScreen(DPNSContestedNamesScreen { + dpns_subscreen: DPNSSubscreen::Owned, + .. + }) => ScreenType::DPNSMyUsernames, Screen::TransitionVisualizerScreen(_) => ScreenType::TransitionVisualizer, Screen::WithdrawalScreen(screen) => { ScreenType::WithdrawalScreen(screen.identity.clone())