From 273c1f1878e0c129752d63e01a577adaa5083d69 Mon Sep 17 00:00:00 2001 From: James Power Date: Sat, 14 Mar 2026 22:48:48 -0400 Subject: [PATCH] Declutter nav --- CHANGELOG.md | 3 + .../Controllers/Client/ClientContainer.swift | 4 ++ .../Client/SSClientViewController.m | 69 ++++++++++++++++++- .../Client/SSMusicPickerViewController.swift | 21 ++++-- .../Client/SSWorldDisplayController.m | 1 - .../Settings/SSSettingsViewController.h | 4 ++ .../Settings/SSSettingsViewController.m | 12 ++++ src/Mudrammer/Views/SPLMUDTitleView.h | 6 ++ src/Mudrammer/Views/SPLMUDTitleView.m | 25 ++++++- src/Mudrammer/WorldStore/MusicLibrary.swift | 5 +- 10 files changed, 136 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df9e438..745b49c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added +- Clean up nav bar: move music to settings, conditional play/pause (#34) - Replace isIPad device checks with size classes and trait collections (#24) - Replace deprecated UITableViewCellAccessoryDetailDisclosureButton in SSWorldCell (#19) - Replace hardcoded 320pt popover widths with adaptive sizing (#18) @@ -29,6 +30,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Add Swift model classes for World, Alias, Trigger, Gag, Ticker (#76) ### Fixed +- Fix MSSP Server Status button not receiving taps in title view (#35) +- Fix world select button on iPhone (toggleSidebar collapsed) (#33) - Fix crash in itemWithAttributedString: on empty string (#57) - Fix libtelnet missing HAVE_ZLIB define causing MCCP2 gibberish (#56) diff --git a/src/Mudrammer/Controllers/Client/ClientContainer.swift b/src/Mudrammer/Controllers/Client/ClientContainer.swift index b808f9d..e884de9 100644 --- a/src/Mudrammer/Controllers/Client/ClientContainer.swift +++ b/src/Mudrammer/Controllers/Client/ClientContainer.swift @@ -157,6 +157,10 @@ class ClientContainer: UISplitViewController { } @objc func toggleSidebar() { + if isCollapsed { + show(.primary) + return + } #if os(visionOS) let shouldHide = displayMode == .oneBesideSecondary #else diff --git a/src/Mudrammer/Controllers/Client/SSClientViewController.m b/src/Mudrammer/Controllers/Client/SSClientViewController.m index 281754e..37a2c4f 100644 --- a/src/Mudrammer/Controllers/Client/SSClientViewController.m +++ b/src/Mudrammer/Controllers/Client/SSClientViewController.m @@ -77,6 +77,7 @@ - (void) appendTextToLog:(NSString *)cleanText; @property (nonatomic, strong) UIBarButtonItem *editWorldButton; @property (nonatomic, strong) UIBarButtonItem *musicButton; @property (nonatomic, strong) UIBarButtonItem *playPauseButton; +@property (nonatomic, strong) UIBarButtonItem *serverStatusButton; @property (nonatomic, strong) UIViewController *SSPopoverController; @@ -167,6 +168,7 @@ - (SSClientViewController *) init { [client sendNAWS]; }]; } + } return self; @@ -189,13 +191,14 @@ + (SSClientViewController *)clientWithWorld:(NSString *)worldIdentifier { } - (UIRectEdge)edgesForExtendedLayout { - return UIRectEdgeBottom; + return UIRectEdgeAll; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self setNavVisible:YES]; + [self updateWorldToolbar]; // Hacks around initial welcome on iPhone if ([self isConnected]) { @@ -211,6 +214,7 @@ - (void)viewDidAppear:(BOOL)animated { } } + - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; @@ -307,14 +311,26 @@ - (void)updateWorldToolbar { self.connectButton.connectDelegate = self; } + BOOL isRegularWidth = self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular; + + // On regular width, MSSP is shown as a bar button instead of a title subtitle + self.titleView.hidesMSSPSubtitle = isRegularWidth; + [self.titleView setMSSPData:self.titleView.MSSPData]; + NSMutableArray *leftItems = [NSMutableArray array]; [leftItems addObject:self.settingsButton]; if (currentWorldIdentifier) { [leftItems addObject:self.editWorldButton]; - [leftItems addObject:self.musicButton]; - [leftItems addObject:self.playPauseButton]; + + if (isRegularWidth) { + [leftItems addObject:self.musicButton]; + } + + if (self.gmcpHandler.isMusicPlaying || self.gmcpHandler.isMusicPaused) { + [leftItems addObject:self.playPauseButton]; + } } self.navigationController.navigationBar.tintColor = [UIColor whiteColor]; @@ -330,6 +346,16 @@ - (void)updateWorldToolbar { [rightItems addObject:[self.worldSelectButton wrappedBarButtonItem]]; + if (isRegularWidth && self.titleView.MSSPData.count > 0) { + if (!self.serverStatusButton) { + _serverStatusButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"SERVER_STATUS", nil) + style:UIBarButtonItemStylePlain + target:self + action:@selector(tappedServerStatus:)]; + } + [rightItems addObject:self.serverStatusButton]; + } + [self.navigationItem setRightBarButtonItems:rightItems animated:NO]; } @@ -352,6 +378,10 @@ - (void)viewDidLoad { // setup navbar [self updateWorldToolbar]; + + // rebuild toolbar when horizontal size class changes (e.g. rotation, multitasking) + [self registerForTraitChanges:@[UITraitHorizontalSizeClass.class] + withAction:@selector(updateWorldToolbar)]; } #pragma clang diagnostic push @@ -608,6 +638,20 @@ - (void)tappedPlayPause:(id)sender { - (void)musicStateDidChange:(NSNotification *)note { [self updateMusicButtons]; + [self updateWorldToolbar]; +} + +- (void)tappedServerStatus:(id)sender { + NSDictionary *data = self.titleView.MSSPData; + if (!data || data.count == 0) return; + + SPLMSSPViewController *MSSPVC = [[SPLMSSPViewController alloc] initWithMSSPData:data]; + MSSPVC.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone + target:self + action:@selector(dismissMSSPViewController:)]; + UINavigationController *nav = [MSSPVC wrappedNavigationController]; + nav.modalPresentationStyle = UIModalPresentationFormSheet; + [self presentViewController:nav animated:YES completion:nil]; } - (void)tappedWorldSelect:(id)sender { @@ -722,6 +766,24 @@ - (void)settingsViewShouldOpenContact:(SSSettingsViewController *)settingsViewCo [self closeSettingsWithCompletion:nil]; } +- (void)settingsViewShouldOpenMusicPicker:(SSSettingsViewController *)settingsViewController + navigationController:(UINavigationController *)navigationController { + SSMusicPickerViewController *picker = [[SSMusicPickerViewController alloc] init]; + picker.gmcpHandler = self.gmcpHandler; + picker.currentWorldIdentifier = currentWorldIdentifier; + + if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) { + // On compact width, push onto the settings nav stack + picker.navigationItem.leftBarButtonItem = nil; + [navigationController pushViewController:picker animated:YES]; + } else { + // On regular width (popover), dismiss settings then present music picker + [self closeSettingsWithCompletion:^{ + [self tappedMusic:nil]; + }]; + } +} + - (void)settingsViewShouldSendSessionLog:(SSSettingsViewController *)settingsViewController { @weakify(self); @@ -1262,6 +1324,7 @@ - (BOOL)mudsocketShouldAttemptSSL:(SSMUDSocket *)socket { - (void)mudsocket:(SSMUDSocket *)socket receivedMSSPData:(NSDictionary *)MSSPData { [self.titleView setMSSPData:MSSPData]; + [self updateWorldToolbar]; } - (void)mudsocket:(SSMUDSocket *)socket receivedGMCPModule:(NSString *)module data:(NSDictionary *)data { diff --git a/src/Mudrammer/Controllers/Client/SSMusicPickerViewController.swift b/src/Mudrammer/Controllers/Client/SSMusicPickerViewController.swift index 5f61dc9..7fa1fa4 100644 --- a/src/Mudrammer/Controllers/Client/SSMusicPickerViewController.swift +++ b/src/Mudrammer/Controllers/Client/SSMusicPickerViewController.swift @@ -22,15 +22,22 @@ class SSMusicPickerViewController: UITableViewController { SSThemes.configureTable(tableView) tracks = MusicLibrary.shared.musicTrackDictionaries - navigationItem.leftBarButtonItem = UIBarButtonItem( - barButtonSystemItem: .close, - target: self, - action: #selector(dismissPicker) - ) + // Only show close button when presented modally (not pushed onto a nav stack) + if navigationController?.viewControllers.first === self || navigationController == nil { + navigationItem.leftBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .close, + target: self, + action: #selector(dismissPicker) + ) + } } @objc private func dismissPicker() { - dismiss(animated: true) + if navigationController?.viewControllers.first !== self { + navigationController?.popViewController(animated: true) + } else { + dismiss(animated: true) + } } // MARK: - UITableViewDataSource @@ -77,6 +84,6 @@ class SSMusicPickerViewController: UITableViewController { world.ambientMusicPath = path WorldStoreBridge.updateMUDWorld(world) } - dismiss(animated: true) + dismissPicker() } } diff --git a/src/Mudrammer/Controllers/Client/SSWorldDisplayController.m b/src/Mudrammer/Controllers/Client/SSWorldDisplayController.m index 62e94d5..67b2421 100644 --- a/src/Mudrammer/Controllers/Client/SSWorldDisplayController.m +++ b/src/Mudrammer/Controllers/Client/SSWorldDisplayController.m @@ -331,7 +331,6 @@ - (void)addClientWithWorld:(NSString *)worldIdentifier { newClient.delegate = self; UINavigationController *nav = [newClient wrappedNavigationController]; nav.delegate = newClient; - nav.navigationBar.translucent = NO; BOOL isFirstWorld = [self numberOfClients] == 0; diff --git a/src/Mudrammer/Controllers/Settings/SSSettingsViewController.h b/src/Mudrammer/Controllers/Settings/SSSettingsViewController.h index 7e9e590..0bed262 100644 --- a/src/Mudrammer/Controllers/Settings/SSSettingsViewController.h +++ b/src/Mudrammer/Controllers/Settings/SSSettingsViewController.h @@ -23,6 +23,7 @@ typedef NS_ENUM( NSUInteger, SettingsSection ) { typedef NS_ENUM( NSUInteger, SettingsTopSectionRow ) { SettingsRowWorlds = 0, SettingsRowThemes, + SettingsRowMusic, SettingsTopNumRows }; @@ -63,4 +64,7 @@ typedef NS_ENUM( NSUInteger, SettingsAboutHelpRow ) { - (void) settingsViewShouldOpenContact:(SSSettingsViewController *)settingsViewController; +- (void) settingsViewShouldOpenMusicPicker:(SSSettingsViewController *)settingsViewController + navigationController:(UINavigationController *)navigationController; + @end diff --git a/src/Mudrammer/Controllers/Settings/SSSettingsViewController.m b/src/Mudrammer/Controllers/Settings/SSSettingsViewController.m index dda8dd3..86f243b 100644 --- a/src/Mudrammer/Controllers/Settings/SSSettingsViewController.m +++ b/src/Mudrammer/Controllers/Settings/SSSettingsViewController.m @@ -253,6 +253,12 @@ - (void)viewDidLoad { cell.textLabel.text = NSLocalizedString(@"THEMES", @"Themes"); cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + break; + case SettingsRowMusic: + + cell.textLabel.text = NSLocalizedString(@"BACKGROUND_MUSIC", @"Background Music"); + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + break; default: break; @@ -451,6 +457,12 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath case SettingsRowThemes: nextVC = [SSThemePickerController themePickerController]; + break; + case SettingsRowMusic: + if ([del respondsToSelector:@selector(settingsViewShouldOpenMusicPicker:navigationController:)]) { + [del settingsViewShouldOpenMusicPicker:self + navigationController:self.navigationController]; + } break; default: break; diff --git a/src/Mudrammer/Views/SPLMUDTitleView.h b/src/Mudrammer/Views/SPLMUDTitleView.h index 3a9bdc2..45f1b94 100644 --- a/src/Mudrammer/Views/SPLMUDTitleView.h +++ b/src/Mudrammer/Views/SPLMUDTitleView.h @@ -32,6 +32,12 @@ */ - (void) setTitle:(NSString *)title; +/** + * When YES, hides the MSSP subtitle button even if MSSPData is set. + * Use when MSSP is accessible via a separate bar button item. + */ +@property (nonatomic, assign) BOOL hidesMSSPSubtitle; + /** * Block called when there is MSSP data available and the button has been tapped. */ diff --git a/src/Mudrammer/Views/SPLMUDTitleView.m b/src/Mudrammer/Views/SPLMUDTitleView.m index 9eba9bd..b7775a9 100644 --- a/src/Mudrammer/Views/SPLMUDTitleView.m +++ b/src/Mudrammer/Views/SPLMUDTitleView.m @@ -20,6 +20,7 @@ @implementation SPLMUDTitleView - (instancetype)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { + self.clipsToBounds = NO; _titleLabel = [UILabel new]; self.titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; @@ -41,6 +42,8 @@ - (instancetype)initWithFrame:(CGRect)frame { forState:UIControlStateNormal]; [self.MSSPButton.titleLabel setFont:[UIFont systemFontOfSize:13.f]]; + self.MSSPButton.tintColor = [UIColor whiteColor]; + [self.MSSPButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [self.MSSPButton addTarget:self action:@selector(MSSPButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; @@ -55,6 +58,25 @@ - (void)MSSPButtonTapped:(id)sender { } } +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { + // The MSSP button may render outside our bounds (nav bar can shrink the title view). + // Extend hit testing to include it. + if (self.MSSPButton.superview == self) { + CGPoint buttonPoint = [self convertPoint:point toView:self.MSSPButton]; + if ([self.MSSPButton pointInside:buttonPoint withEvent:event]) { + return self.MSSPButton; + } + } + return [super hitTest:point withEvent:event]; +} + +- (CGSize)intrinsicContentSize { + if (self.MSSPData && self.MSSPData.count > 0) { + return CGSizeMake(UIViewNoIntrinsicMetric, 36); + } + return CGSizeMake(UIViewNoIntrinsicMetric, 22); +} + - (void)setTitle:(NSString *)title { self.titleLabel.text = title; [self setNeedsDisplay]; @@ -63,7 +85,7 @@ - (void)setTitle:(NSString *)title { - (void)setMSSPData:(NSDictionary *)MSSPData { _MSSPData = MSSPData; - if (MSSPData && [MSSPData count] > 0) { + if (MSSPData && [MSSPData count] > 0 && !self.hidesMSSPSubtitle) { [self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.and.right.and.top.equalTo(self); make.height.equalTo(@18); @@ -83,6 +105,7 @@ - (void)setMSSPData:(NSDictionary *)MSSPData { [self.MSSPButton removeFromSuperview]; } + [self invalidateIntrinsicContentSize]; [self setNeedsLayout]; [UIView animateWithDuration:0.3f diff --git a/src/Mudrammer/WorldStore/MusicLibrary.swift b/src/Mudrammer/WorldStore/MusicLibrary.swift index 1629fc6..5efd1d8 100644 --- a/src/Mudrammer/WorldStore/MusicLibrary.swift +++ b/src/Mudrammer/WorldStore/MusicLibrary.swift @@ -196,10 +196,11 @@ final class MusicLibrary: NSObject { // MARK: - Path Building /// Build the relative path within GMCPMedia/ for a track. - /// Mirrors the logic in GMCPMediaManager.cachePath(baseURL:name:). + /// Must mirror GMCPMediaManager.cachePath(baseURL:name:) exactly. static func buildRelativePath(hostname: String, name: String, baseURL: URL) -> String { var components: [String] = [] - components.append(hostname) + // Use baseURL.host (not the hostname parameter) to match cachePath behavior. + if let host = baseURL.host { components.append(host) } let basePath = baseURL.path.trimmingCharacters(in: CharacterSet(charactersIn: "/")) if !basePath.isEmpty { components.append(basePath) } components.append(name)